What are surrogate pairs?
In JavaScript, '😀'.length is 2, not 1. The reason is surrogate pairs — the trick UTF-16 uses to store characters beyond the “basic” range using two 16-bit units.
The basic vs supplementary planes
UTF-16 stores code points up to U+FFFF (the Basic Multilingual Plane) in a single 16-bit unit. Characters above that — including most emoji — live in “supplementary” planes and need two units: a high surrogate (U+D800–U+DBFF) followed by a low surrogate (U+DC00–U+DFFF).
Why length lies
JavaScript strings expose UTF-16 units, so a supplementary character counts as 2. Iterating by code unit splits emoji; iterating with for…of or the spread operator respects code points:
'😀'.length // 2
[...'😀'].length // 1
for (const ch of '😀') // one iteration
Don't slice blindly
Cutting a string at an arbitrary index can land between a surrogate pair and produce an invalid “lone surrogate.” Use code-point- or grapheme-aware operations for user text.
Frequently asked questions
Why is emoji length 2 in JavaScript?
JS strings are UTF-16; emoji outside the basic plane are stored as a surrogate pair of two units, so .length counts 2.
How do I count emoji correctly?
Iterate by code point ([...str], for…of) or by grapheme with Intl.Segmenter.