Emoji Encoding Guide: UTF-8, UTF-16 & Surrogate Pairs

Why Emoji Break Your String.length

If you've ever been surprised that '😀'.length === 2 in JavaScript, you've encountered one of the most common emoji encoding pitfalls. This guide explains why it happens and how to handle emoji correctly in code.

The Three Unicode Encodings

Unicode defines three encoding forms. Each uses a different strategy to convert code points into bytes:

UTF-8UTF-8
문자당 1~4바이트를 사용하는 가변 너비 유니코드 인코딩으로, 웹의 98% 이상에서 사용됩니다.
: The Web Standard

UTF-8 uses 1-4 bytes per character and is backward-compatible with ASCII:

Code Point Range Bytes Example
U+0000 - U+007F 1 byte A → 0x41
U+0080 - U+07FF 2 bytes é → 0xC3 0xA9
U+0800 - U+FFFF 3 bytes 한 → 0xED 0x95 0x9C
U+10000 - U+10FFFF 4 bytes 😀 → 0xF0 0x9F 0x98 0x80

Every emoji requires 4 bytes in UTF-8 because emoji code points are above U+FFFF. A ZWJ영폭 결합자 (ZWJ)
여러 이모지를 하나의 합성 이모지로 결합하는 데 사용되는 보이지 않는 유니코드 문자(U+200D).
sequence like 👩‍💻 uses 11 bytes (4 + 3 + 4 for person + ZWJ + laptop).

UTF-16UTF-16
문자당 2 또는 4바이트를 사용하는 가변 너비 유니코드 인코딩으로, JavaScript와 Java에서 사용됩니다.
: JavaScript and Java's Native Encoding

UTF-16 uses 2 or 4 bytes per character:

Code Point Range Code Units Example
U+0000 - U+FFFF (BMP) 1 unit (2 bytes) A → 0x0041
U+10000 - U+10FFFF (SMP보충 다국어 평면 (SMP)
대부분의 이모지 코드 포인트가 할당된 유니코드 평면 1(U+10000~U+1FFFF).
)
2 units (4 bytes) 😀 → 0xD83D 0xDE00

Characters above U+FFFF — including virtually all emoji — require a surrogate pair: two 16-bit code units that encode one code point.

UTF-32UTF-32
문자당 정확히 4바이트를 사용하는 고정 너비 유니코드 인코딩으로, 직접적인 코드 포인트 매핑을 제공합니다.
: Simple but Wasteful

UTF-32 uses exactly 4 bytes per code point. Simple for processing (string[i] always gives you one code point), but uses 4x the memory of ASCII text.

Surrogate Pairs Explained

Surrogate pairs are the key to understanding JavaScript emoji behavior. Here's the math:

Code Point: U+1F600 (😀)
Offset: 0x1F600 - 0x10000 = 0xF600

High Surrogate: 0xD800 + (0xF600 >> 10) = 0xD800 + 0x3D = 0xD83D
Low Surrogate:  0xDC00 + (0xF600 & 0x3FF) = 0xDC00 + 0x200 = 0xDE00

Result: 0xD83D 0xDE00

This is why '😀'.charCodeAt(0) returns 55357 (0xD83D) — the high surrogate — and '😀'.charCodeAt(1) returns 56832 (0xDE00) — the low surrogate.

Common Pitfalls

1. String Length

// WRONG: counts UTF-16 code units
'😀'.length              // 2
'👨‍👩‍👧'.length           // 8

// CORRECT: counts grapheme clusters
[...new Intl.Segmenter().segment('👨‍👩‍👧')].length  // 1

2. String Slicing

// WRONG: splits surrogate pair
'Hello 😀'.slice(0, 7)  // 'Hello \uD83D' (broken!)

// CORRECT: use spread or Array.from
[...'Hello 😀'].slice(0, 7).join('')  // 'Hello 😀'

3. Regular Expressions

// WRONG: . doesn't match emoji by default
/^.$/.test('😀')  // false

// CORRECT: use u flag for Unicode awareness
/^.$/u.test('😀')  // true

4. Database Storage

When using MySQL, ensure your column uses utf8mb4 (not utf8 which only supports 3-byte characters). PostgreSQL handles this correctly by default with its TEXT type.

-- MySQL: must use utf8mb4 for emoji
ALTER TABLE posts MODIFY content TEXT CHARACTER SET utf8mb4;

Language-Specific Tips

Python 3

Python 3 handles emoji gracefully — len('😀') returns 1 because Python uses code points internally.

emoji = '👩‍💻'
len(emoji)           # 3 (code points: woman + ZWJ + laptop)
emoji.encode('utf-8') # b'\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x92\xbb'

Java

Java strings are UTF-16, like JavaScript:

"😀".length()          // 2 (surrogate pair)
"😀".codePointCount(0, "😀".length())  // 1 (actual code point count)

Rust

Rust strings are UTF-8 by default and distinguish between bytes, chars, and graphemes:

"😀".len()            // 4 (bytes)
"😀".chars().count()  // 1 (code points)

Analyze Any Emoji's Encoding

Use our Sequence Analyzer to see the complete encoding breakdown of any emoji — UTF-8 bytes, UTF-16 surrogates, code points, and component roles.

관련 도구

🔍 시퀀스 분석기 시퀀스 분석기
ZWJ 시퀀스, 피부톤 수정자, 키캡 시퀀스, 국기 쌍을 개별 구성 요소로 분해합니다.

용어 사전 항목

이모지 이모지
일본어 '그림 문자(絵文字)'를 뜻하며, 디지털 커뮤니케이션에서 감정과 개념을 표현하는 작은 그래픽 기호.
유니코드 유니코드
모든 문자 체계와 이모지를 포함하여 각 문자에 고유한 번호를 부여하는 범용 문자 인코딩 표준.
서로게이트 쌍 서로게이트 쌍
U+FFFF 이상의 문자를 나타내기 위해 사용되는 두 개의 UTF-16 코드 유닛.
영폭 결합자 (ZWJ) 영폭 결합자 (ZWJ)
여러 이모지를 하나의 합성 이모지로 결합하는 데 사용되는 보이지 않는 유니코드 문자(U+200D).
코드 포인트 코드 포인트
유니코드 표준에서 각 문자에 부여되는 고유한 숫자 값으로, U+XXXX 형식으로 표기됩니다.
보충 다국어 평면 (SMP) 보충 다국어 평면 (SMP)
대부분의 이모지 코드 포인트가 할당된 유니코드 평면 1(U+10000~U+1FFFF).
UTF-16 UTF-16
문자당 2 또는 4바이트를 사용하는 가변 너비 유니코드 인코딩으로, JavaScript와 Java에서 사용됩니다.
UTF-32 UTF-32
문자당 정확히 4바이트를 사용하는 고정 너비 유니코드 인코딩으로, 직접적인 코드 포인트 매핑을 제공합니다.
UTF-8 UTF-8
문자당 1~4바이트를 사용하는 가변 너비 유니코드 인코딩으로, 웹의 98% 이상에서 사용됩니다.

관련 스토리