Learning LabExplorable explanations
← All artifacts
Computer Systems

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.

unicodeutf-8encodingstringscomputer-systems
LiveInteractive · drag, toggle, run it
Computer Systems · Sequel to The Interactive Byte

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.

The input
Presets:
Four ways to count the same string

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.

Graphemes
4
what a human calls characters
Code points
4
integers Unicode assigns
UTF-16 units
5
JS .length counts these
UTF-8 bytes
10
what gets stored or sent

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.

Layer 1 · graphemes

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.

A#0
é#1
#2
😀#3
Layer 2 and 3 · code points and their UTF-8 bytes

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.

ASCII range, fits in one byte with a leading 0.
code point in binary (7 payload bits)
1000001
packed into UTF-8 bytes
0x41
marker bitspayload bits0x41
The UTF-8 scheme

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.

1 byte
0xxxxxxxU+0000 to U+007F
2 bytes
110xxxxx 10xxxxxxto U+07FF
3 bytes
1110xxxx 10xxxxxx 10xxxxxxto U+FFFF
4 bytes
11110xxx 10xxxxxx 10xxxxxx 10xxxxxxto U+10FFFF
The words, kept straight
characterAn informal word. People mean a grapheme, but a program almost never does, which is the source of most confusion.
graphemeOne unit a reader perceives. May be several code points (a flag, an accented letter, a family emoji). This is the human sense of character.
code pointAn integer Unicode assigns to an abstract character, from U+0000 up to U+10FFFF. Written U+0041. The assignment is all Unicode does; it says nothing about bytes.
code unitThe fixed-size piece of one encoding. UTF-16 uses 16-bit units, so a code point above U+FFFF needs two of them, a surrogate pair. JavaScript strings are UTF-16, so .length counts these units, which is why it disagrees with everything else.
byteEight bits, the unit of storage and transmission. UTF-8 turns each code point into one to four of them. ASCII is the subset that fits in one byte with a leading 0, so any ASCII text is already valid UTF-8.

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.

Graphemes via Intl.Segmenter, code points via string iteration, bytes via UTF-8 bit packing and TextEncoder.