I recently saw an interesting YouTube video by “Physics for the Birds”. The main video is about matrixes, but he used flags to explain some of the operations on matrixes:

He broke down how you could break down flags across its axes to represent them as matrixes, and save some storage space. In the end, this was used to circle back to actual matrixes and calculations on them.
However this inspired me: Flags are often so simple - just some colored stripes, common elements like a star, crescent or cross. Using some custom encoding scheme, shouldn’t defining the 🇫🇷French flag like “3 stripes, Blue, White, Red” with some custom decoder and renderer only take a handful of bits? How would such an encoding look like?
So I tried to create one:
Requirements
To start, I put down some acceptance criteria for my encoding:
- The decoded flag has to be "recognizable enough”
- Details don't have to be exactly correct. Small deviations, inaccuracies are ok - as long as somebody can look at it and say "Ah that's that flag!"
- This includes things like the exact placement, shape details of objects.
- This also includes the exact color. I know how proud France is of their new shade of blue, but for this encoding we can break down any blue tone into some middle blue
- Only country flags
- No state flags, city flags, or other vexillological designs
- No 🇳🇵Nepal
- Sorry Nepal, I like your non-rectangular flag but this would just complicate stuff
- Simple flags should require very little bits, while very intricate flags are allowed to take up some more bits - so no fixed length
- No coat of arms
- Flags like 🇦🇩 Andorra contain their own coat of arms, for which you pretty much need some custom bitmap/vector to render it. That would just put some SVG or something in the tiny format so no
What makes a flag a flag?
To start, I needed to figure out what elements the common flag has. Worldometers List of all country flags was very helpful for this:
First thing: Damn, flags are more varied than I thought. Of course you have the simple striped ones like 🇫🇷France, 🇮🇹Italy or 🇩🇪Germany. Then you have designs like 🇧🇮Burundi or 🇧🇦Bosnia with stars, sections and everything. 🇲🇰North Macedonia and 🇸🇨Seychelles with radial stripes. 🇨🇿Czechia, 🇧🇸Bahamas and more with custom triangles at the left edge and so many more.
So what do most flags have in common. From what I see:
- Stripes
- Stripes, lot’s of stripes: 🇫🇷Horizontal, 🇩🇪Vertical, 🇵🇱 2 Stripes, 🇦🇲3 Stripes, 🇺🇸13 Stripes, 🇨🇴 Uneven stripes, 🇨🇬Angled Stripes
- Common Shapes
- 🇻🇳Stars, 🇩🇿Crescents, 🇯🇵Circles
- At custom sizes and positions - 🇻🇳one or 🇺🇸many
- Left color triangle
- Suprisingly common and in many colors - but often the same general shape 🇰🇲🇧🇸🇨🇿
- Top left corner colored
- Some kind of custom rectangle happening in the top left 🇺🇸🇬🇷
- Union Jack
- 🇬🇧🇦🇺🇳🇿🇹🇻
- And then the nordic countries
- They’re definitely a big plus to have… 🇳🇴🇸🇪🇩🇰🇫🇮
Breaking down the flag
From what I can tell, the protocol should define these elements:
- Aspect Ratio
- Flags all have their individual aspect ratios but there are clear patterns: around 45% of flags have the ratio 2:3, around 28% have 1:2, around 9% have 3:5 - and then some longtail of other formats
- Color palette
- As defined, we’ll focus on general color tones instead of relicating the exact color code. So we’ll focus on color groups like Blue, Green, Yellow
- Once again we have a similar picture: Surpisingly a lot of red, then white, blue, yellow/gold, green, black and orange - and then a longtail or other colors
- Layers
- I think it makes sense to define the actual contents using a handful of “Layers” instead of trying to hardcode all the common things separately. Each layer can then define its own list of options to supply
- This should work like in Photoshop or other apps: e.g. for the 🇺🇸US Flag you’d first have the stripe layer, then a layer for the blue rectangle, then a star layer
- The most common layer will be the “Stripe” layer. This can have options for the amount of stripes and its colors, the direction, even or uneven distribution, and repeating patterns
- Other layers should be a “Shapes” layer for stars, crecents and others - defining the position and rotation. A “Band” layer and a “Region” layer to color a specific rectangle
Turning it into bits
As with most things, so many aspects of flags also seem to follow Zipf’s Law - aspect ratio, colors used, elements on it (Stripes, stars etc.). To keep common elements short I thus decided to encode almost everything into its own Huffman Tree, assigning nice short binary codes for the common cases.
To allow the longtail to exist without creating a huge tree, I decided to cut off all values that only exist in a single flag and instead set the last leaf of the tree to a “Custom” value, followed by a fixed-length free value option. For example, this allowed 🇸🇻El Salvador to keep its 189:335 aspect ratio without needing that in the tree itself.
As an example, here is the full Huffman tree for the flag aspect ratio:
graph TD %% Internal Nodes root((Root)) n1(( )) n11(( )) n110(( )) n1101(( )) n11011(( )) n111(( )) n1110(( )) n11100(( )) n111001(( )) n11101(( )) n111010(( )) n111011(( )) n1111(( )) n11110(( )) n111100(( )) n111101(( )) n11111(( )) n111110(( )) n1111100(( )) n1111101(( )) n111111(( )) n1111110(( )) n1111111(( )) n11111111(( )) %% Leaf Nodes (Ratios) L_2_3[2:3] L_1_2[1:2] L_3_5[3:5] L_5_8[5:8] L_10_19[10:19] L_3_4[3:4] L_4_7[4:7] L_1_1[1:1] L_7_10[7:10] L_8_11[8:11] L_11_18[11:18] L_11_20[11:20] L_11_28[11:28] L_18_25[18:25] L_1_phi[1:φ] L_4_5[4:5] L_6_7[6:7] L_10_17[10:17] L_13_15[13:15] L_15_22[15:22] L_16_25[16:25] L_189_335[189:335] L_28_37[28:37] L_5_7[5:7] L_7_11[7:11] L_CUSTOM[CUSTOM] %% Left Branch (0...) root -- 0 --> L_2_3 root -- 1 --> n1 n1 -- 0 --> L_1_2 n1 -- 1 --> n11 %% 110... Branch n11 -- 0 --> n110 n110 -- 0 --> L_3_5 n110 -- 1 --> n1101 n1101 -- 0 --> L_5_8 n1101 -- 1 --> n11011 n11011 -- 0 --> L_10_19 n11011 -- 1 --> L_3_4 %% 111... Branch n11 -- 1 --> n111 n111 -- 0 --> n1110 %% 1110... Sub-branches n1110 -- 0 --> n11100 n11100 -- 0 --> L_4_7 n11100 -- 1 --> n111001 n111001 -- 0 --> L_1_1 n111001 -- 1 --> L_7_10 n1110 -- 1 --> n11101 n11101 -- 0 --> n111010 n111010 -- 0 --> L_8_11 n111010 -- 1 --> L_11_18 n111011 -- 0 --> L_11_20 n111011 -- 1 --> L_11_28 n11101 -- 1 --> n111011 %% 1111... Branch n111 -- 1 --> n1111 n1111 -- 0 --> n11110 %% 11110... Sub-branches n11110 -- 0 --> n111100 n111100 -- 0 --> L_18_25 n111100 -- 1 --> L_1_phi n1111001(( )) n11110 -- 1 --> n111101 n111101 -- 0 --> L_4_5 n111101 -- 1 --> L_6_7 %% 11111... Branch n1111 -- 1 --> n11111 n11111 -- 0 --> n111110 %% 111110... Sub-branches n111110 -- 0 --> n1111100 n1111100 -- 0 --> L_10_17 n1111100 -- 1 --> L_13_15 n111110 -- 1 --> n1111101 n1111101 -- 0 --> L_15_22 n1111101 -- 1 --> L_16_25 %% 111111... Deepest Branch n11111 -- 1 --> n111111 n111111 -- 0 --> n1111110 n1111110 -- 0 --> L_189_335 n1111110 -- 1 --> L_28_37 n111111 -- 1 --> n1111111 n1111111 -- 0 --> L_5_7 n1111111 -- 1 --> n11111111 n11111111 -- 0 --> L_7_11 n11111111 -- 1 --> L_CUSTOM %% Styling for better readability classDef leaf fill:#e1f5fe,stroke:#0288d1,stroke-width:2px; classDef internal fill:#eceff1,stroke:#607d8b,stroke-width:1px; class L_2_3,L_1_2,L_3_5,L_5_8,L_10_19,L_3_4,L_4_7,L_1_1,L_7_10,L_8_11,L_11_18,L_11_20,L_11_28,L_18_25,L_1_phi,L_4_5,L_6_7,L_10_17,L_13_15,L_15_22,L_16_25,L_189_335,L_28_37,L_5_7,L_7_11,L_CUSTOM leaf; class root,n1,n11,n110,n1101,n11011,n111,n1110,n11100,n111001,n11101,n111010,n111011,n1111,n11110,n111100,n111101,n11111,n111110,n1111100,n1111101,n111111,n1111110,n1111111,n11111111 internal;
So for the 45% of flags with an aspect ratio of 2:3, all that’s needed is setting the first bit to 0.
The format then contains Huffman trees for:
- Aspect Ratio (Most common: 2:3, 1:2, 3:5)
- Custom Width:Height for longtail
- Color Palette size (Most common: 3, 2, 4, 5)
- Custom “Count - 7” for longtail, since the tree goes up to 7
- Color (Most common: Red, White, Blue)
- Custom colors can be given as compact 10-bit RGB approximation (RRR GGGG BBB)
- Number of layers
- Layer Type (Stripes, Shape, Regions, Cross, Band, Buildin)
- Some more specialized subtrees
- e.g. Number of points on the star, shape placement
The whole format is pretty much just one Huffman tree walk after the other to define what the flag is made of.
With this, we can try encoding our first flag into the format. For this, I’ll take 🇮🇩Indonesia, as its definitely our encoding winner, or I guess “The most average flag”:
- A 2:3 aspect ratio (1 bit to encode) →
0 - the most common aspect ratio
- A color palette size of 2 colors (2 bits) →
10 - This is the only place it looses a bit, since 3 colors is the most common in the tree
- Then definining the 2 colors: Red (2 bits) →
00and white (2 bits) →01 - the 2 top-most colors in the tree
- A layer count of 1 layer (1 bit) →
0 - top of the tree
- A stripe layer (1 bit) →
0, “palette-equal” mode (i.e. give every color in the palette one equal stripe, 1 bit) →0and horizontal stripes (1 bit) →0 - All the top of their Huffman tree
- → All combined:
0 10 00 01 0 0 0 0, or base64 encoded “QgA=”
Using this format, the average flag can be represented in 76 bits, with a median of 55 bits.
The longest one is 🇶🇦Qatar at 420bits:
#gHR1Y$?-+]m.0xS3F!0{.UH{uDppW5u2^+s|6~(p@GwHH<N:?57K99\(s)~!G4`! . I’d say that flag can only barely be encoded into our format: It has a zig-zagged edge between the sides, which I encoded as 11 individual rectangle layers.Union Jack 🇬🇧
You could say I cheated a little here. The union jack is so common but so complicated to build from layers that I just put the whole thing as just a build-in shape in the protocol. So instead of building it, a flag can just specify “Union Jack Layer in the lop left” to place it.
Encoding it
I first used base64 to simply turn the bits into saveable text. With its 6 payload bits per ASCII byte, the average flag definition needed 14 characters with a median of 12 characters. The shortest flag code is “
QgA=” for Indonesias flag.However, I decided to use Base94, which uses every visible one-byte ASCII character from “!” through “~” to try improving the efficiency of encoding some more.
I also thought of some Emoji-based encoding, but since every character would require more than 1 byte to save there, the reduced character count would probably still require mote bits in total. (Also I didn’t want to risk encoding a countries flag as “💩🤮👎” or something like that…)
With this, we’re down to an average of 12 characters for a flag, with a median of 9 characters. Indonesia stays the shortest encoding: “
<F”.Rendering it
I used ChatGPT Codex to turn the format into a two-step system: A encoder/decoder and an SVG renderer.
The decoder first turns the binary blob into a readable format. For 🇮🇩Indonesias flag we already broke down, this looks like this:
{ "aspectRatio": { "kind": "rational", "height": "2", "width": "3" }, "palette": [ { "r": 210, "g": 16, "b": 52 }, { "r": 255, "g": 255, "b": 255 } ], "layers": [ { "kind": "stripes", "direction": "horizontal", "stripes": [ { "color": 0 }, { "color": 1 } ] } ] }
The renderer then takes this decoded flag and turns it into an SVG code like this:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1.5 1"> <rect x="0" y="0" width="1.5" height="0.5" fill="#d21034"/> <rect x="0" y="0.5" width="1.5" height="0.5" fill="#fff"/> </svg>
The code behind it is pretty nice, with some separate class and interfaces for the encoded binary parts and layers. However, even tsc-compiled the encoder/decoder sits at 27kB and the renderer at 12.5kB - maybe a bit much after just optimizing every bit of the source format…
So I went back to Codex to create an alternative “minidecoder”: Combining decoding and rendering into one pass instead of two separate engines, getting rid of the nice interface/class infrastructure, and just boiling everything down to small primitive functions.
WIth this, we got down to one 470-line TypeScript file, 5.29 kB (2.66 kB gzipped) when compiled.
Flags that aren’t covered
With this (rather primitive) format I managed to encode 128 flags with varying success.
However, there we also 67 flags that I couldn’t manage to encode. Some notable reasons:
- 🇪🇸 Spain, 🇬🇶 Equatorial Guinea, 🇦🇩 Andorra, 🇧🇿 Belize, 🇧🇳 Brunei, 🇰🇭 Cambodia, 🇨🇷 Costa Rica, 🇭🇷 Croatia, 🇩🇴 Dominican Republic, 🇪🇨 Ecuador, 🇸🇻 El Salvador, 🇫🇯 Fiji, 🇭🇹 Haiti, 🇲🇽 Mexico, 🇲🇩 Moldova, 🇲🇪 Montenegro, 🇳🇮 Nicaragua, 🇴🇲 Oman, 🇵🇾 Paraguay, 🇵🇹 Portugal, 🇸🇲 San Marino, 🇷🇸 Serbia, 🇸🇰 Slovakia, 🇸🇮 Slovenia, 🇻🇪 Venezuela
- Contain a coat of arms, seal or national emblem (25)
- 🇦🇴 Angola, 🇧🇧 Barbados, 🇸🇿 Eswatini, 🇬🇹 Guatemala, 🇰🇪 Kenya, 🇱🇸 Lesotho, 🇱🇮 Liechtenstein, 🇲🇹 Malta, 🇲🇿 Mozambique, 🇹🇯 Tajikistan, 🇻🇦 Vatican City
- Object glyphs such as weapons, tools, crowns, shields or a hat (11)
- 🇦🇱 Albania, 🇧🇹 Bhutan, 🇩🇲 Dominica, 🇪🇬 Egypt, 🇰🇮 Kiribati, 🇵🇬 Papua New Guinea, 🇱🇰 Sri Lanka, 🇺🇬 Uganda, 🇿🇲 Zambia, 🇿🇼 Zimbabwe
- Animal glyphs, mostly eagles and other birds (10)
- 🇨🇦 Canada, 🇨🇾 Cyprus, 🇪🇷 Eritrea, 🇬🇩 Grenada, 🇱🇧 Lebanon
- Plant glyphs such as leaves, branches or a nutmeg (5)
- 🇦🇬 Antigua and Barbuda, 🇧🇷 Brazil, 🇳🇵 Nepal, 🇿🇦 South Africa, 🇻🇺 Vanuatu
- Geometry the layer model cannot express, such as Y-shapes, V-fields, a globe or a non-rectangular outline (5)
- 🇦🇫 Afghanistan, 🇮🇷 Iran, 🇮🇶 Iraq, 🇸🇦 Saudi Arabia
- Arabic text or calligraphy (4)
- Adding some custom Text layer could help here
- 🇮🇳 India, 🇰🇬 Kyrgyzstan, 🇲🇳 Mongolia, 🇰🇷 South Korea
- Religious or cultural symbols such as the Ashoka Chakra, tunduk, Soyombo or Taegeuk (4)
- 🇧🇾 Belarus, 🇰🇿 Kazakhstan, 🇹🇲 Turkmenistan
- Ornamental hoist patterns
→ So pretty much all contain some custom glyph or text that’s not easily representable with out geometry-based layers
Random flags!
Now we have kind of a structured language of what a flag might look like, so I decided to add a randomizer to fill a new flag with random items from the various Huffman trees we have.






…yeah I guess I can see some country having a flag like this?
All in all
I’m sure others can find even more bit-saving methods or completely different ways of compressing or laying out the data. However it was still quite a lot of fun to go from a list of all flags, finding common elements and searching for ways to pack as much data as possible into the bits. Finally getting to use my Huffman Coding knowledge from my Bachelors and writing some bit-level code was also very fun.
You can find the final page with the flags on https://vantezzen.github.io/miniflags/ and the source code at . Please don’t expect super clean code there - in the end it was mostly vibe coded based on my format ideas. All details about the format are documented at .