Explain the role of regex based pre-tokenization in cl100k_base (GPT-4) and o200k_base (GPT-4o). Focus specifically on how each handles Unicode ZWJ (Zero Width Joiner) emoji sequences, and why the o200k_base regex was updated.
Neither regex has a ZWJ rule: cl100k_base keeps a ZWJ emoji in one chunk, and o200k_base may even split it at U+FE0F. o200k's lower count (6 vs 9 for 🏳️🌈) comes from its larger vocabulary.
Picture a sticker made of three smaller stickers taped together with invisible tape: a face, a tape strip, and a heart that together mean one thing. Both the older and the newer system keep the taped sticker in one bag. But the price is set by the price list, and the newer system has a longer price list with cheaper bundles for the pieces. The invisible tape is the zero-width joiner, an unprintable character that glues emoji parts into one symbol, and the price is how many tokens the text costs.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Emoji look like single characters. To a tokenizer, though, they are often short sequences: a base symbol, an invisible joiner, and one or more further symbols that a renderer fuses into one glyph. That gap between what you see and what the bytes contain is why the same rainbow flag can cost nine tokens on one OpenAI model and six on another.
Three layers that are usually discussed separately work together here: Unicode grapheme clusters, the pre-tokenization regex, and the BPE merge constraint. People who think emoji are single codepoints cannot explain the fragmentation. People who think BPE can stitch fragments back together miss the hard boundary rule. People who credit the emoji savings to a regex rule for emoji miss that the vocabulary is the real lever here.
What a ZWJ emoji sequence is in bytes
A surprising number of emoji are not single Unicode codepoints. They are grapheme clusters: several codepoints that a text renderer combines into one visible glyph. The glue is the zero-width joiner, codepoint U+200D, which prints nothing but tells the renderer to fuse its neighbours. A rainbow flag, for example, is a waving white flag, a variation selector that requests emoji presentation, the joiner at U+200D, and a rainbow. That is four codepoints for one glyph.
In UTF-8, each of those codepoints expands to multiple bytes. The joiner alone is three bytes, and the emoji codepoints are four bytes each. So a single rainbow flag is 14 raw bytes before any tokenization decision is made. Skin tone variants, family emoji, and many flags follow the same pattern: a base plus modifiers plus joiners.
The visible glyph is built from a sequence. How many tokens it costs depends on two things: where the regex cuts the sequence, and which byte merges the vocabulary has. Nothing about the picture you see determines that.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
import tiktoken
cl100k = tiktoken.get_encoding("cl100k_base")
o200k = tiktoken.get_encoding("o200k_base")
flag = "\U0001F3F3\uFE0F\u200D\U0001F308" # rainbow flag (ZWJ sequence)
print(len(cl100k.encode(flag))) # 9: one chunk, few emoji merges in the vocabulary
print(len(o200k.encode(flag))) # 6: two chunks, but more byte merges in the vocabulary
# Always resolve the encoding from the model so the regex matches:
enc = tiktoken.encoding_for_model("gpt-4o") # -> o200k_base| Aspect | cl100k_base (GPT-4) | o200k_base (GPT-4o) |
|---|---|---|
| ZWJ emoji sequence | One chunk (symbol branch; no ZWJ rule) | Usually one chunk; split after U+FE0F, which counts as a mark |
| Tokens for the rainbow flag 🏳️🌈 | 9 (few emoji merges) | 6 (more byte merges) |
| Emoji modifiers (skin tone, flags) | One chunk, many byte-level tokens | One chunk, fewer tokens |
| Multilingual character runs | Breaks at combining marks (Devanagari vowel signs) | Keeps combining marks with their letters |
| Design motivation | ASCII-tuned | Multilingual and multimodal |
Real products, models, and research that use this idea.
- OpenAI's o200k_base, used by GPT-4o and GPT-5, encodes common emoji in fewer tokens than cl100k_base (6 versus 9 for the rainbow flag), mainly because its vocabulary has more byte merges.
- Messaging and social products with heavy emoji usage see lower per-message token bills when they move to o200k-based models for the same text.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow would you estimate the token-cost savings of moving an emoji-heavy chat product from a cl100k model to a GPT-4o model?
Sample real messages, encode under both encodings with tiktoken, weight the per-message token delta by traffic volume.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Assuming o200k_base added a regex rule that keeps ZWJ emoji whole; neither regex mentions U+200D, and o200k's savings on emoji come from its larger vocabulary.
60 second bullets to scan on the way to the call.
What a ZWJ emoji sequence is at the codepoint level
Why pre-tokenization cuts are walls BPE cannot cross
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.