Why string.length Lies: Grapheme Clusters and Emoji Length

The Lie Your Runtime Tells You

'👨‍👩‍👧‍👦'.length  // 11

That is not 11 characters. It is one family emoji. The .length property in JavaScript counts UTF-16UTF-16
1文字あたり2または4バイトを使用する可変長Unicode符号化方式。JavaScript、Java、Windowsが内部的に使用している。
code units, not visible characters. For most ASCII text this distinction is invisible, but emoji expose the gap brutally.

Understanding why this happens — and how to fix it — requires knowing what a grapheme cluster is.

Three Levels of "Character"

Unicode defines multiple levels of text unit, each useful for different purposes:

Level Unit JavaScript Python
Byte Raw storage unit — len(s.encode('utf-8UTF-8
1文字あたり1〜4バイトを使用する可変長Unicode符号化方式。ウェブで最も普及しており、98%以上のウェブサイトで使用されている。
'))
Code unit Encoding unit s.length —
Code point Unicode scalar value [...s].length len(s)
Grapheme cluster User-perceived character Intl.Segmenter grapheme library

A grapheme cluster is what a human thinks of as "one character" — one visible glyph on screen. For emoji, this is always at least one code point, but often several combined.

What Makes an Emoji "Long"?

Basic Emoji: 1 Code Point, 2 Code Units

😀 = U+1F600
UTF-16: 0xD83D 0xDE00 (surrogate pair)
.length in JS: 2
Python len(): 1

Emoji + Variation Selector: 2 Code Points

❤️ = U+2764 + U+FE0F
.length in JS: 2
Python len(): 2
Grapheme clusters: 1

Emoji + Skin Tone: 2 Code Points

👍🏽 = U+1F44D + U+1F3FD
.length in JS: 4 (two surrogate pairs)
Python len(): 2
Grapheme clusters: 1

ZWJゼロ幅接合子(ZWJ) (ZWJ)
複数の絵文字を1つの複合絵文字に結合するために使われる不可視のUnicode文字(U+200D)。人物と物体を組み合わせた職業絵文字などに使用される。
Sequence: 3+ Code Points

👩‍💻 = U+1F469 + U+200D + U+1F4BB
.length in JS: 5
Python len(): 3
Grapheme clusters: 1

Family Emoji: 7 Code Points

👨‍👩‍👧‍👦 = 👨 + ZWJ + 👩 + ZWJ + 👧 + ZWJ + 👦
.length in JS: 11
Python len(): 7
Grapheme clusters: 1

Correct Counting in JavaScript

Using Intl.Segmenter (Modern, Built-In)

Intl.Segmenter is available in all modern browsers and Node.js 16+. It segments text by grapheme clusters:

function countGraphemes(str) {
  const segmenter = new Intl.Segmenter();
  return [...segmenter.segment(str)].length;
}

countGraphemes('Hello')     // 5 — correct
countGraphemes('Hello 😀') // 7 — correct
countGraphemes('👨‍👩‍👧‍👦')   // 1 — correct!
countGraphemes('👩🏽‍💻')    // 1 — correct!

Slicing Text Correctly

Slicing with .slice() on emoji strings corrupts surrogate pairs:

const s = 'Hello 👋';

// WRONG: may cut through a surrogate pair
s.slice(0, 7)  // 'Hello \uD83D' — broken!

// CORRECT: spread to code points first
[...s].slice(0, 7).join('')  // 'Hello 👋' — correct

// EVEN BETTER: slice by grapheme clusters
function sliceByGrapheme(str, start, end) {
  const segmenter = new Intl.Segmenter();
  const segments = [...segmenter.segment(str)];
  return segments.slice(start, end).map(s => s.segment).join('');
}

sliceByGrapheme('Hello 👋 World', 0, 7)  // 'Hello 👋'

String Reversal

Reversing an emoji string with .split('').reverse().join('') produces garbage:

// WRONG: reverses code units, breaks surrogates
'Hello 😀'.split('').reverse().join('')  // '😀 olleH' (actually garbled)

// CORRECT: reverse by code points
[...'Hello 😀'].reverse().join('')  // '😀 olleH'

// BEST: reverse by grapheme clusters
function reverseGraphemes(str) {
  const segmenter = new Intl.Segmenter();
  return [...segmenter.segment(str)]
    .map(s => s.segment)
    .reverse()
    .join('');
}

reverseGraphemes('Hello 👨‍👩‍👧‍👦')  // '👨‍👩‍👧‍👦 olleH'

Correct Counting in Python

Python 3's len() counts code points, not grapheme clusters. For most ASCII text this is identical to grapheme count, but for emoji it is not.

text = '👨‍👩‍👧‍👦'
len(text)  # 7 — code points (wrong for "visible characters")

Using the grapheme Library

import grapheme

grapheme.length('👨‍👩‍👧‍👦')   # 1 — correct
grapheme.length('Hello 😀')  # 7 — correct

# Slice by grapheme clusters
grapheme.slice('Hello 👨‍👩‍👧‍👦', 0, 7)  # 'Hello 👨‍👩‍👧‍👦'

# Iterate graphemes
list(grapheme.graphemes('Hi 👋'))
# ['H', 'i', ' ', '👋']

Using the regex Module with \X

The \X pattern in the regex module matches grapheme clusters:

import regex

def count_graphemes(text):
    return len(regex.findall(r'\X', text))

count_graphemes('👨‍👩‍👧‍👦')   # 1
count_graphemes('Hello 👨‍👩‍👧‍👦')  # 7

Other Languages

Java

// Java uses UTF-16 internally like JavaScript
"👨‍👩‍👧‍👦".length()  // 11 (code units)

// Count code points
"👨‍👩‍👧‍👦".codePointCount(0, "👨‍👩‍👧‍👦".length())  // 7

// Count grapheme clusters (requires ICUICU (ICU)
絵文字処理を含むUnicodeと国際化のサポートを提供する、広く使われているオープンソースライブラリ「International Components for Unicode」。
) BreakIterator bi = BreakIterator.getCharacterInstance(); bi.setText("👨‍👩‍👧‍👦"); int count = 0; while (bi.next() != BreakIterator.DONE) count++; // count = 1

Swift

Swift's String is designed around grapheme clusters — count returns grapheme count by default:

"👨‍👩‍👧‍👦".count  // 1 — correct out of the box!

This is one area where Swift's string model is superior to most other languages.

Rust

// Rust len() returns bytes
"👨‍👩‍👧‍👦".len()  // 25 (bytes)

// chars() iterates code points
"👨‍👩‍👧‍👦".chars().count()  // 7

// For grapheme clusters, use the unicode-segmentation crate
use unicode_segmentation::UnicodeSegmentation;
"👨‍👩‍👧‍👦".graphemes(true).count()  // 1

Practical Implications

Input Validation / Character Limits

If you show users "max 280 characters" and count by .length in JavaScript, users can enter far fewer emoji than expected (or you silently truncate their input). Count by grapheme clusters for user-facing limits.

Database Column Sizing

Ensure column size limits account for multi-code-point emoji. A VARCHAR(10) that accepts emoji-heavy input needs to be measured in code points (PostgreSQL) or bytes (MySQL with utf8mb4), not graphemes.

Text Rendering

Layout engines that use pixel widths handle emoji correctly by nature — they measure bounding boxes, not code units. But manual text layout (canvas, PDF generation, terminal output) needs grapheme-aware iteration.

Use our Sequence Analyzer to see exactly how many code points, code units, and grapheme clusters any emoji string contains — making these abstract concepts concrete.

関連ツール

🔍 シーケンスアナライザー シーケンスアナライザー
ZWJ シーケンス、肌の色の修飾子、キーキャップシーケンス、国旗のペアを個別のコンポーネントに分解します。

用語集の項目

ICU (ICU) ICU (ICU)
絵文字処理を含むUnicodeと国際化のサポートを提供する、広く使われているオープンソースライブラリ「International Components for Unicode」。
UTF-16 UTF-16
1文字あたり2または4バイトを使用する可変長Unicode符号化方式。JavaScript、Java、Windowsが内部的に使用している。
UTF-8 UTF-8
1文字あたり1〜4バイトを使用する可変長Unicode符号化方式。ウェブで最も普及しており、98%以上のウェブサイトで使用されている。
コードポイント コードポイント
Unicode規格で各文字に割り当てられた固有の数値。U+XXXX形式で表記される(例:😀はU+1F600)。
コード単位 コード単位
文字を符号化するための最小ビット単位。UTF-8では8ビット、UTF-16では16ビット、UTF-32では32ビット。
サロゲートペア サロゲートペア
U+FFFFを超える文字を表すために使用される2つのUTF-16コード単位の組み合わせ。上位サロゲート(U+D800〜U+DBFF)と下位サロゲート(U+DC00〜U+DFFF)で構成される。
ゼロ幅接合子(ZWJ) (ZWJ) ゼロ幅接合子(ZWJ) (ZWJ)
複数の絵文字を1つの複合絵文字に結合するために使われる不可視のUnicode文字(U+200D)。人物と物体を組み合わせた職業絵文字などに使用される。
ユニコード ユニコード
すべての文字体系と絵文字を含む、あらゆる文字にユニークな番号を割り当てる汎用文字符号化規格。
書記素クラスター 書記素クラスター
複数のUnicodeコードポイントで構成されていても、1つの視覚的な単位として表示されるユーザーが認識する文字。
異体字セレクタ (VS) 異体字セレクタ (VS)
文字をテキスト形式(モノクロ)で表示するかどうかを切り替えるUnicode文字。VS-15(U+FE0E)がテキスト表示、VS-16(U+FE0F)が絵文字表示を指定する。
絵文字 絵文字
「絵文字」は「えもじ」と読む日本語で「絵+文字」を意味する。デジタルコミュニケーションでアイデア・感情・物事を表現するために使われる小さなグラフィック記号。

関連ストーリー