UTF-8 and Unicode
One emoji can be four bytes, and string length lies about it. Type any text and watch it split into graphemes you see, the code points Unicode assigns, and the UTF-8 bytes that get stored.
UTF-8 and Unicode
A byte holds a number from 0 to 255, but text is not numbers. To store text a computer needs two separate ideas. Unicode hands every character a code point, a plain integer like U+1F600. UTF-8 is one way to pack that integer back into bytes. Type a string below and watch it fall through the layers: the glyphs you see, the code points underneath, and the actual bytes on the wire.
Ask how long a string is and you get four different answers depending on what you count. This is why "length" is ambiguous: in JavaScript it returns UTF-16 code units, not characters.
These four numbers disagree, which is the whole point. One visible glyph can be several code points, and one code point can be several bytes. Asking for the length without saying length of what gives you the wrong answer.
A grapheme cluster is one unit of writing the way a reader sees it. A flag, a family emoji, or a letter with a combining accent each look like a single character, yet each is built from several code points underneath. This split is found with Intl.Segmenter.
Iterating the string with for..of yields true code points, not UTF-16 units. Open any row to see its code point written in binary, then watch those bits get packed into UTF-8 bytes. The bytes are checked against the real TextEncoder output.
UTF-8 is variable length. The leading bits of the first byte announce how many bytes follow, and every continuation byte starts with 10 so a decoder can never lose its place. The marker bits are shown in red, the payload bits that carry the code point in terracotta.
.length counts these units, which is why it disagrees with everything else.Surrogate pairs belong to UTF-16, not UTF-8. UTF-8 encodes a code point above U+FFFF directly as four bytes, so it never needs surrogates. Every byte grid above is computed from the code point, then verified against TextEncoder, so the marker and payload bits you see are the genuine encoding, not a lookup table.