Direct Technical Answer
JavaScript strings are sequences of 16-bit UTF-16 code units, not individual Unicode characters or user-perceived symbols. Supplementary characters outside the Basic Multilingual Plane (such as emojis and rare ideographs) require two UTF-16 code units (a surrogate pair), while composite graphemes require multiple code points.
The difference between UTF-16 code units, Unicode code points, and grapheme clusters in JavaScript
const emoji = '😀';
// 1. String.prototype.length counts UTF-16 code units
console.log(emoji.length); // 2
// 2. String iterator ([...str]) counts Unicode code points
console.log([...emoji].length); // 1
// 3. Composite emoji with Zero-Width Joiner (ZWJ)
const family = '👨👦';
console.log(family.length); // 5 code units
console.log([...family].length); // 3 code points
// 4. Intl.Segmenter counts user-perceived grapheme clusters
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
console.log([...segmenter.segment(family)].length); // 1 user character
2
1
5
3
1
Why This Happens: The ECMAScript String Model
To understand JavaScript string behavior, developers must understand the three distinct layers of Unicode representation in the browser and Node.js.
1. The UTF-16 Historical Legacy
2. Code Units vs Code Points
3. Code Points vs User-Perceived Grapheme Clusters
4. Lone Surrogates and Ill-Formed Strings
Language & Runtime Execution Model
V8 uses an internal representation optimization: strings containing only Latin-1 characters (U+0000..U+00FF) are stored as 1-byte arrays (SeqOneByteString). Strings containing any character above U+00FF are promoted to 2-byte UTF-16 arrays (SeqTwoByteString).
Common Mistakes vs Production Patterns
Learn which patterns fail in production and the modern standards-compliant alternatives.
Counting User-Visible Characters
Character counter in UI forms, SMS limits, bio fields.const str = '👨💻';
console.log(str.length);
// Returns 5 (UTF-16 code units)
const str = '👨💻';
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
console.log([...segmenter.segment(str)].length);
// Returns 1 (true user-perceived character)
Iterating Characters in a String
Parsing tokens, building custom string transformers.const text = '🔥hello';
for (let i = 0; i < text.length; i++) {
console.log(text[i]); // splits 🔥 into \uD83D and \uDD25!
}
const chars = text.split(''); // ['\uD83D', '\uDD25', 'h', 'e', 'l', 'l', 'o']
const text = '🔥hello';
for (const char of text) {
console.log(char); // '🔥', 'h', 'e', 'l', 'l', 'o'
}
const chars = [...text]; // ['🔥', 'h', 'e', 'l', 'l', 'o']
Reading Numerical Code Points
Character encoding converters, cryptographic hashing, font metrics.const char = '😀';
console.log(char.charCodeAt(0).toString(16));
// Returns 'd83d' (High surrogate only!)
const char = '😀';
console.log(char.codePointAt(0).toString(16));
// Returns '1f600' (True Unicode code point!)
Writing Unicode Escape Sequences
Source code literals, JSON payloads, regex definitions.const fire = '\uD83D\uDD25'; // 🔥
// Prone to transposition, hard to read, manual math needed
const fire = '\u{1F525}'; // 🔥
const smile = '\u{1F600}'; // 😀
Edge Cases & Invariants Matrix
A diverse cross-character test matrix comparing character behavior across ASCII, combining marks, emojis, flags, and surrogate fragments.
| Test Case | Input Glyph | Code Units | Code Points | Graphemes | Category | Technical Explanation |
|---|---|---|---|---|---|---|
| ASCII Latin Letter | A |
1
|
1
|
1
|
Basic Latin | U+0041 fits into a single 7-bit byte, 16-bit code unit, and code point. |
| Precomposed Accent | é |
1
|
1
|
1
|
Latin-1 Supplement | U+00E9 precomposed acute accent in BMP. 1 code unit, 1 code point, 1 grapheme. |
| Decomposed Accent | é |
2
|
2
|
1
|
Combining Mark | Base letter e (U+0065) + Combining Acute (U+0301). 2 code points render as 1 visual grapheme. |
| Currency Symbol | € |
1
|
1
|
1
|
Currency | Euro sign U+20AC located in BMP. 1 code unit, 1 code point, 1 grapheme. |
| Standard Emoji | 😀 |
2
|
1
|
1
|
Supplementary Plane | Grinning Face U+1F600 in Plane 1. Encoded as 2 UTF-16 code units (D83D DE00). |
| ZWJ Emoji Sequence | 👨👦 |
5
|
3
|
1
|
Emoji Sequence | Man (U+1F468) + ZWJ (U+200D) + Boy (U+1F466). 3 code points joined as 1 family glyph. |
| Rainbow Flag | 🏳️🌈 |
6
|
4
|
1
|
Emoji Sequence | White Flag (U+1F3F3) + Variation Selector 16 (U+FE0F) + ZWJ (U+200D) + Rainbow (U+1F308). |
| National Flag (Regional Indicators) | 🇺🇳 |
4
|
2
|
1
|
Flag Sequence | Regional Indicator U (U+1F1FA) + Regional Indicator N (U+1F1F3). 2 code points, 1 grapheme. |
| Lone High Surrogate |
1
|
1
|
1
|
Surrogate Fragment | Isolated high surrogate 0xD83D. Valid in ECMAScript memory, but ill-formed in UTF-8. |
Core API Reference
Standard library methods, runtime signatures, and Unicode semantics.
str.codePointAt(index: number): number | undefined
Returns the Unicode code point value (integer) at the specified UTF-16 code unit index.
String.fromCodePoint(...codePoints: number[]): string
Creates a string from a sequence of 21-bit Unicode code points.
new Intl.Segmenter(locales?: string | string[], options?: { granularity: "grapheme" | "word" | "sentence" })
Provides locale-aware text segmentation, enabling reliable user-perceived grapheme cluster iteration.
str.normalize(form?: "NFC" | "NFD" | "NFKC" | "NFKD"): string
Returns the Unicode Normalization Form of the calling string.
new RegExp(pattern, "v")
ECMAScript 2024 UnicodeSets regex flag enabling set operations and string properties.
Interactive JavaScript String & Unicode Inspector
Test any string or emoji sequence in real time to inspect its UTF-16 code units, Unicode code points, and grapheme clusters in your browser.
Code Point Breakdown:
Standards & Source Provenance
All technical invariants, APIs, and behaviors in this guide are verified against official primary specifications.
ECMAScript 2024 Language Specification (ECMA-262 15th Edition)
Clause: §6.1.4 The String Type & §22.1 String Objects
ECMAScript Internationalization API (ECMA-402)
Clause: §18 Intl.Segmenter Objects
Unicode Standard Annex #29: Unicode Text Segmentation
Clause: §3 Grapheme Cluster Boundaries
Unicode Standard Annex #15: Unicode Normalization Forms
Clause: §1 Normalization Forms
JavaScript String Reference & UTF-16 Representation
Clause: UTF-16 Characters, Unicode Code Points, and Grapheme Clusters