What happens to emoji ZWJ sequences under cl100k_base vs. o200k_base pre-tokenization?
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.
cl100k_base splits ZWJ emoji at the joiner so they fragment into many tokens; o200k_base keeps the whole sequence in one chunk, so common emoji tokenize far more cheaply.
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. The older system sees the invisible tape and rips the sticker into separate pieces, charging you for each one. The newer system recognizes the taped-together sticker as a single unit and can give it one price tag. The invisible tape is the zero-width joiner, an unprintable character that glues emoji parts into one symbol, and the price tag is how many tokens the text costs.
Detailed answer & concept explanation~6 min readEverything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example.
Everything important, quickly.
4 min: ZWJ emoji are multi-codepoint clusters + pre-tokenization cuts are walls + cl100k splits at the joiner + o200k keeps the sequence intact + fewer tokens for emoji and CJK + driven by GPT-4o multilingual goals + counts not portable across encodings.
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))) # more tokens: cluster fragments at the ZWJ
print(len(o200k.encode(flag))) # fewer tokens: ZWJ sequence kept intact
# 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 | Split at the joiner into components | Kept as one pre-token unit |
| Tokens per ZWJ flag emoji | Several (multi-byte fallback) | One to two if frequent |
| Emoji modifiers (skin tone, flags) | Often fragmented | Recognized as a unit |
| Multilingual character runs | Awkward boundaries | Grouped more sensibly |
| Design motivation | ASCII-tuned | Multilingual and multimodal |
Real products, models, and research that use this idea.
- OpenAI's o200k_base, shipped with the GPT-5.5 family, recognizes ZWJ emoji and modifier sequences so common emoji cost fewer tokens than under cl100k_base.
- 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.
- tiktoken exposes both encodings, so a team can encode the same emoji string under each and watch the token count drop on o200k_base.
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?
QWhy doesn't keeping ZWJ sequences together help if the specific emoji never appeared in training?
Don't say thisRed flags and common mistakes that signal junior thinking. Click to expand.
Red flags and common mistakes that signal junior thinking. Click to expand.
Assuming a ZWJ emoji is one codepoint, so it should be one token; it is actually several codepoints glued by U+200D, and the regex decides whether they stay together.
The night-before-the-interview bullets. Scan these on the way to the call.
Primary sources. Skim if you want the original framing.
Same topic, related formats. Practice these next.