DevLab
Encoding

UTF-8 Explained: How Computers Store Text

Understand how UTF-8 encodes every writing system into bytes, why it became the universal standard, and how encoding bugs appear in real applications.

The Problem: Characters vs Bytes

Computers store everything as bytes. ASCII defined 128 characters (one byte each) for English in 1963. Unicode assigned a unique code point to every character in every writing system: U+0041 = A, U+00E9 = é, U+1F525 = 🔥. UTF-8 is the encoding that converts those code points into bytes for storage and transmission.

How UTF-8 Works

UTF-8 is variable-width: different characters use 1-4 bytes.

  • ASCII characters (A-Z, 0-9, punctuation) → 1 byte (identical to ASCII)
  • Latin diacritics (é, ü, ñ) → 2 bytes
  • CJK characters, Arabic, Hebrew → 3 bytes
  • Emoji and supplementary characters → 4 bytes
// JavaScript quirk (internally UTF-16, not UTF-8):
"A".length        // → 1
"é".length        // → 1
"🔥".length       // → 2 (counts UTF-16 code units, not characters!)
[..."🔥"].length  // → 1 (spread uses Unicode-aware iteration)

Why UTF-8 Won

  • ASCII compatibility: Any ASCII document is valid UTF-8 — no migration needed
  • Space efficiency: English text uses 1 byte per character; UTF-32 (fixed 4 bytes) would triple file sizes
  • Universal standard: HTML5, JSON, PostgreSQL, and the web all default to UTF-8

Common Encoding Bugs

// Mojibake: reading UTF-8 as Latin-1
// é in UTF-8 = bytes 0xC3 0xA9
// Interpreted as Latin-1: "é"

// MySQL "utf8" is NOT full UTF-8 — only supports 3-byte sequences
-- Emoji (4 bytes) silently fail or throw errors!
-- Fix: always use utf8mb4
ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Practical Rules

  • Set Content-Type: application/json; charset=utf-8 on API responses
  • Use utf8mb4 in MySQL/MariaDB — never utf8
  • Always specify encoding when reading files: fs.readFileSync(path, 'utf8')
  • Never assume string length equals byte length when checking database field limits

Practice with these tools

More Learning Topics

RegexRegex Basics: A Complete Beginner's GuideRegexRegex Special Characters: Complete ReferenceRegexRegex Groups and Captures ExplainedRegexRegex Quantifiers: Complete GuideCSSCSS Selectors: The Complete GuideCSSCSS Specificity: Why Your Styles Aren't ApplyingJSONJSONPath Syntax: Query JSON Like XPathTimeUnix Timestamps ExplainedEncodingBase64 Encoding ExplainedEncodingJWT Structure and How It WorksEncodingJWT vs Session Tokens: Which Should You Use?EncodingJWT Refresh Tokens ExplainedCryptoHash Functions Explained: MD5, SHA-256, and When to Use EachEncodingURL Encoding Explained: What %20 Actually MeansJSONJSON Schema Explained: Validate Your JSON DataJSONJSON vs YAML: Which Should You Use?JSONJSON.stringify and JSON.parse: Edge Cases You Should KnowRegexRegex Lookahead and Lookbehind: Match Without ConsumingRegexRegex for Email Validation: The Right ApproachCSSThe CSS Box Model: margin, padding, border, and contentCSSFlexbox vs CSS Grid: When to Use EachCSSCSS Custom Properties (Variables) ExplainedTimeISO 8601 Explained: The Right Way to Format DatesTimeUnix Timestamps vs ISO 8601: Which to Use in Your API?TextCORS Explained: Why Your API Call is BlockedTextHTTP Status Codes: A Practical Developer GuideRegexNamed Capture Groups in Regex: Clean ExtractionsColorsColor Spaces Explained: RGB, HSL, HEX, and BeyondColorsColor Contrast for Developers: WCAG Rules and How to CheckNumbersNumber Bases Explained: Binary, Octal, Decimal, and HexNumbersBitwise Operations for Web DevelopersHTMLHTML Semantic Elements: A Complete ReferenceTextText Encoding for Developers: ASCII, UTF-8, and UnicodeRegexWhat is Regex? Complete Guide for DevelopersJSONJSON Format Explained: Structure, Syntax, and Common ErrorsEncodingHow JWT Works: Header, Payload, Signature DecodedHTMLHow to Convert Any Website to Markdown (for LLMs, RAG & Docs)TextPreparing Website Content for RAG: Clean Markdown Pipelines