JavaScript's Unicode Adventure

An interactive guide to why old JavaScript methods can break modern characters.

The Basics: Code Points and Encodings

A Unicode character (or "code point") is a unique number for a symbol (e.g., U+0041 for 'A'). The number of bytes it uses depends on the encoding:

Surrogate Pairs & Grapheme Clusters

In JavaScript (UTF-16), characters outside the basic set require a surrogate pair (two 2-byte units) to represent one symbol. Even more complex are grapheme clusters, where multiple characters (some using surrogate pairs) join to form a single visual glyph, like ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ.

This is the root of the problem: older JS methods operate on the 2-byte units, not the final visual symbols.

Examples of Complex Characters

Char Description Type .length
๐Ÿ˜‚ Face with Tears of Joy Surrogate Pair 2
๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ป Woman Technologist Grapheme Cluster 7
๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Family Grapheme Cluster 11

A Closer Look: The Composition of ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ

The family emoji is formed by "gluing" four different emojis together with the invisible Zero Width Joiner (ZWJ) character.

  • Man (๐Ÿ‘จ): surrogate pair (len: 2)
  • ZWJ (โ€): single unit (len: 1)
  • Woman (๐Ÿ‘ฉ): surrogate pair (len: 2)
  • ZWJ (โ€): single unit (len: 1)
  • Girl (๐Ÿ‘ง): surrogate pair (len: 2)
  • ZWJ (โ€): single unit (len: 1)
  • Boy (๐Ÿ‘ฆ): surrogate pair (len: 2)

The total .length of 11 comes from adding the seven parts: 2 + 1 + 2 + 1 + 2 + 1 + 2 = 11.

Test Case 1: A Single Surrogate Pair ('๐„ž')

First, let's test the musical G-clef symbol (๐„ž). It's a single visual character represented by one surrogate pair (\uD834\uDD1E).

๐„ž

Legacy Methods (The Problem)

'๐„ž'.length

'๐„ž'.charAt(0)

Modern Methods (The Solution)

Array.from('๐„ž')

for (const char of '๐„ž')

Regular Expressions

/^.$/.test('๐„ž') (no 'u' flag)

/^.$/u.test('๐„ž') (with 'u' flag)

Test Case 2: A Complex Grapheme Cluster ('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ')

Now let's test the family emoji. It's a single visual glyph made of 7 distinct Unicode code points. This shows the limits of even some modern methods.

๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ

Legacy Methods (The Problem)

'๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ'.length

'๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ'.charAt(0)

Modern Methods (The Solution)

Array.from('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ')

for (const char of '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ')

Regular Expressions

/^.$/.test('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ') (no 'u' flag)

/^.$/u.test('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ') (with 'u' flag)