The Encoding Problem
Computers store numbers, not letters. Text encoding is the mapping from characters to numbers. When you save a file, your editor encodes each character as bytes. When you open it, the reader decodes those bytes back to characters. If the reader uses a different encoding than the writer, you get garbled text — "mojibake" like "é" instead of "é".
Understanding encoding is not optional for web developers. HTTP headers, HTML meta tags, database collations, API responses, and file I/O all depend on getting encoding right.
ASCII — The Original 128 Characters
ASCII (1963) maps 128 characters to numbers 0-127: English letters, digits, punctuation, and control characters (newline, tab, null). Each character fits in 7 bits. ASCII is the foundation of every modern encoding — the first 128 code points in Unicode are identical to ASCII.
ASCII's limitation is obvious: 128 characters cannot represent Chinese, Arabic, Hindi, emoji, or even accented European characters. Every attempt to extend ASCII (Latin-1, Windows-1252, Shift-JIS) covered only one language group, creating incompatible encodings.
Unicode — One Number Per Character
Unicode (1991) assigns a unique number (code point) to every character in every writing system: over 154,000 characters across 168 scripts. Code points are written as U+0041 (Latin A), U+4E16 (世), U+1F600 (😀).
Unicode is a character set, not an encoding. It says "A is number 65" but does not say how to store 65 as bytes. That is the job of an encoding like UTF-8.
UTF-8 — The Web's Encoding
UTF-8 encodes Unicode code points as 1 to 4 bytes. ASCII characters (U+0000 to U+007F) use 1 byte — identical to ASCII, so every ASCII file is already valid UTF-8. European accented characters use 2 bytes. Chinese, Japanese, and Korean characters use 3 bytes. Emoji use 4 bytes.
UTF-8 is the dominant encoding on the web: over 98% of all websites use it. It is the default in HTML5, JSON (mandated by RFC 8259), Go, Rust, and most modern languages. If you are starting a new project, use UTF-8 everywhere — files, databases, HTTP headers.
- Set in HTML:
<meta charset="utf-8">(must be in the first 1024 bytes) - Set in HTTP:
Content-Type: text/html; charset=utf-8 - Set in databases:
CREATE TABLE ... CHARACTER SET utf8mb4(MySQL) orENCODING 'UTF8'(PostgreSQL)
Common Encoding Bugs
- MySQL utf8 vs utf8mb4 — MySQL's
utf8charset only supports 3-byte UTF-8, which excludes emoji and some CJK characters. Always useutf8mb4. - BOM (Byte Order Mark) — UTF-8 files can start with
EF BB BF(the BOM). Some tools add it, some choke on it. PHP will output the BOM before headers, breaking them. Use UTF-8 without BOM for web files. - String length vs byte length — In JavaScript,
"café".lengthis 4 (characters), but UTF-8 byte length is 5 (é is 2 bytes). Emoji are even trickier:"😀".lengthis 2 in JavaScript (surrogate pair) but 4 bytes in UTF-8. - Double encoding — Encoding already-encoded text creates sequences like
é(UTF-8 bytes of é interpreted as Latin-1, then re-encoded as UTF-8). Fix by ensuring consistent encoding at every boundary.
Practical Rules
Use UTF-8 everywhere. Set charset=utf-8 in HTML, HTTP, and database schemas. Never assume string length equals byte length. Test with non-ASCII input (emoji, CJK, Arabic) early. If you see mojibake, check every boundary where text crosses systems — file, database, HTTP, template engine.