Counting emoji correctly in JavaScript
JavaScript string methods count UTF-16 units, not characters — so emoji quietly break length checks, truncation and reversing. Here's why, and the correct patterns to use.
Three wrong answers and one right one
'😀'.length // 2 (surrogate pair)
'👍🏽'.length // 4 (base + skin tone)
[...'👍🏽'].length // 2 (code points)
const seg = new Intl.Segmenter('en',{granularity:'grapheme'});
[...seg.segment('👍🏽')].length // 1 (what users see)
Iterate by code point
for...of and the spread operator ([...str]) iterate by code point, so they won't split a surrogate pair. Use them instead of index-based for loops for anything user-facing.
Count and slice by grapheme
For character counters, truncation and cursor logic, use Intl.Segmenter with granularity: 'grapheme' so skin tones, flags and ZWJ emoji stay intact.
Rule: never truncate user text by
slice(0, n) on raw indices — you'll eventually cut an emoji in half. Segment first.Frequently asked questions
Why is emoji length wrong in JS?
.length counts UTF-16 code units; emoji use surrogate pairs and sequences, so they count as more than one.
How do I truncate text with emoji safely?
Segment into graphemes with Intl.Segmenter and slice by grapheme, not by raw index.