🛠️ Technical & Developer

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
ترميز Unicode متغير العرض يستخدم من 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)
حرف Unicode غير مرئي (U+200D) يُستخدم لربط عدة رموز تعبيرية في رمز مركّب واحد، كدمج الأشخاص والأشياء في رموز المهن.
sequence like 👩‍💻 uses 11 bytes (4 + 3 + 4 for person + ZWJ + laptop).

UTF-16UTF-16
ترميز Unicode متغير العرض يستخدم 2 أو 4 بايت لكل حرف، وتستخدمه JavaScript وJava وWindows داخليًا.
: 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)
Unicode المستوى 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
ترميز Unicode ثابت العرض يستخدم 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 ومعدّلات لون البشرة وتسلسلات المفاتيح وأزواج الأعلام إلى مكوناتها الفردية.

مصطلحات المسرد

UTF-16 UTF-16
ترميز Unicode متغير العرض يستخدم 2 أو 4 بايت لكل حرف، وتستخدمه JavaScript وJava وWindows داخليًا.
UTF-32 UTF-32
ترميز Unicode ثابت العرض يستخدم 4 بايت بالضبط لكل حرف، مما يوفر تعيينًا مباشرًا لنقاط الرمز على حساب المساحة.
UTF-8 UTF-8
ترميز Unicode متغير العرض يستخدم من 1 إلى 4 بايت لكل حرف، وهو الترميز السائد على الويب (تستخدمه 98%+ من المواقع).
إيموجي إيموجي
كلمة يابانية (絵文字) تعني 'حرف صوري' — رموز رسومية صغيرة تُستخدم في التواصل الرقمي للتعبير عن الأفكار والمشاعر والأشياء.
الزوج البديل الزوج البديل
وحدتا ترميز UTF-16 (بديل عالٍ من U+D800 إلى U+DBFF يتبعه بديل منخفض من U+DC00 إلى U+DFFF) تمثلان معًا حرفًا يتجاوز U+FFFF.
المستوى التكميلي متعدد اللغات (SMP) المستوى التكميلي متعدد اللغات (SMP)
Unicode المستوى 1 (من U+10000 إلى U+1FFFF)، حيث تُخصَّص غالبية نقاط رمز الرموز التعبيرية.
رابط عرضه صفر (ZWJ) رابط عرضه صفر (ZWJ)
حرف Unicode غير مرئي (U+200D) يُستخدم لربط عدة رموز تعبيرية في رمز مركّب واحد، كدمج الأشخاص والأشياء في رموز المهن.
نقطة الرمز نقطة الرمز
قيمة رقمية فريدة مُعيَّنة لكل حرف في معيار Unicode، تُكتب بالصيغة U+XXXX (مثل U+1F600 للرمز 😀).
يونيكود يونيكود
معيار عالمي لتشفير الأحرف يُعيّن رقمًا فريدًا لكل حرف عبر جميع أنظمة الكتابة ومجموعات الرموز، بما في ذلك الرموز التعبيرية.

الرموز التعبيرية ذات الصلة

المقالات ذات الصلة