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.