Why Emoji Break When You Reverse Text
Take "I ❤️ Delhi", reverse it in almost any free tool, and the heart comes back broken — often as a stray mark floating next to a replacement box. The bug is not in the emoji. It is that the word "character" means three incompatible things in programming, and the obvious one is the wrong one.

Reverse the string I ❤️ Delhiand you should get ihleD ❤️ I. Most free
reversers give you something with a broken box in it, or a heart that has lost its
color, or a stray accent-like mark sitting where it does not belong.
The emoji is fine. The reverser is answering a different question than the one you asked.
Three things called "character"
When you ask how many characters are in a piece of text, there are three defensible answers, and they disagree.
UTF-16 code units. How JavaScript stores a string internally, and what
string.lengthreports. Characters outside the original 65,536-slot range are
stored as two units, called a surrogate pair. "🙂".lengthis 2.
Code points. The actual Unicode numbers. [..."🙂"]gives one element, because
spreading a string iterates code points rather than storage units. This is closer
to right, and still not right.
Grapheme clusters. What a reader would point at and call one character. This is the only definition that matches human intuition, and it is the only one that survives reversal.
The gap between the second and third is where emoji live. Consider what a single visible character can actually be made of:
écan be one code point (U+00E9) or two (e+ a combining acute accent).❤️is two code points: a heart, plus U+FE0F, a variation selector that says "render this in color, not as monochrome text".👨👩👧👦is seven code points: four people glued together by three zero-width joiners.- A flag is two regional indicator letters.
🇮🇳is literally the letters I and N in a special alphabet.
Every one of those is one grapheme cluster and more than one code point.
What each wrong answer does to your text
Reverse by UTF-16 code units and you tear surrogate pairs in half. Each half is an unpaired surrogate — not a character at all, just a number in a reserved range that no font can draw. You get the replacement box.
Reverse by code points and the pieces survive individually but end up in the wrong order. This produces the more confusing failures, because nothing is broken in a way a validator would notice:
❤️becomes U+FE0F followed by the heart. The variation selector now modifies whatever character precedes it instead, so your heart turns monochrome and the letter next to it may pick up strange rendering.caféwritten in decomposed form becomeséfacwith the accent attached to the wrong letter — the accent floats over theethat used to be at the other end.- The family emoji dissolves into four people and three invisible joiners in reverse order, which some renderers show as four separate figures and others show as a shorter family plus leftovers.
That second category is worse than the box, because it looks almost right.
The fix, and why it is a one-liner now
Every current browser and Node ships Intl.Segmenter, which splits text into
grapheme clusters according to the Unicode segmentation rules:
function graphemes(text) {
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
return [...segmenter.segment(text)].map((s) => s.segment)
}
const reversed = graphemes(input).reverse().join('')
That is the whole thing. It is not a hack or an approximation — it is the same algorithm your text editor uses to decide what one press of the left arrow key should move past.
Older code hand-rolled this with regular expressions over surrogate ranges and combining-mark categories, which mostly worked and needed updating with every new Unicode release. There is no reason to do that any more.
Why so many tools still get it wrong
Because split('').reverse().join('')is the first result for "reverse a string in
JavaScript", it works perfectly on ASCII, and nobody tests with an emoji. The bug
is invisible until a user pastes in text from a real conversation.
It is a good example of a whole class of text bug: the code is not wrong about anything it was tested on. It is wrong about a definition, and the definition only becomes visible when the input stops being English letters.
How to check a tool in ten seconds
Paste I ❤️ Delhiinto any reverser. If it returns ihleD ❤️ I, it segments
properly. Anything else and it is working at the wrong layer.
Our reverse texttool uses grapheme segmentation, so emoji, flags, and decomposed accents all travel through the reversal intact, and applying the same mode twice returns your input exactly.
The same distinction is why our character counterreports all three numbers side by side instead of picking one. When a form says "maximum 280 characters", it is worth knowing which of the three it means — because for a message with a family emoji in it, the three answers can differ by a factor of ten.