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+D800U+DBFF) followed by a low surrogate (U+DC00U+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.