Have you ever opened a document and seen gibberish like "café" instead of "café"? That's mojibake—garbled text caused by character encoding mismatches. Understanding character encoding is essential for any developer working with text.
The Problem: Computers Only Understand Numbers
Computers don't understand letters, symbols, or emoji. They only work with numbers—specifically, ones and zeros. To store and transmit text, we need a system that maps characters to numbers. That's what character encoding does.
ASCII: The Original Character Set
ASCII (American Standard Code for Information Interchange) was created in 1963 and became the foundation of text encoding. It uses 7 bits to represent 128 characters:
| Range | Characters |
|---|---|
| 0-31 | Control characters (newline, tab, etc.) |
| 32-47 | Punctuation and symbols |
| 48-57 | Digits 0-9 |
| 65-90 | Uppercase A-Z |
| 97-122 | Lowercase a-z |
| 127 | Delete |
Some commonly used ASCII codes:
'A' = 65 'a' = 97 '0' = 48
'B' = 66 'b' = 98 '1' = 49
' ' = 32 '\n' = 10 '\t' = 9
The Limitations of ASCII
ASCII's 128 characters were enough for American English, but the world has far more than 128 characters:
- Accented letters: é, ñ, ü, ø
- Other alphabets: Cyrillic (Привет), Greek (Γειά), Arabic (مرحبا)
- Asian languages: Chinese (你好), Japanese (こんにちは), Korean (안녕)
- Symbols: €, £, ¥, ©, ®
- Emoji: 😀, 🎉, 🌍
Various "extended ASCII" encodings added characters 128-255, but each region created its own:
- ISO-8859-1 (Latin-1): Western European
- ISO-8859-5: Cyrillic
- Windows-1252: Microsoft's Western European
- GB2312: Simplified Chinese
This created chaos: the same byte value meant different characters in different encodings. Document created in one region became unreadable in another.
Unicode: One Encoding to Rule Them All
Unicode was created to solve this by assigning a unique number (called a code point) to every character in every language. The format is U+XXXX where XXXX is a hexadecimal number.
'A' = U+0041
'€' = U+20AC
'中' = U+4E2D
'😀' = U+1F600
Unicode currently defines over 150,000 characters covering:
- 161 modern and historic scripts
- Symbol sets (mathematical, musical, technical)
- Emoji (with more added regularly)
Code points range from U+0000 to U+10FFFF (over 1.1 million possible characters). You can browse common blocks, symbols and their code points in our Unicode character tables.
UTF-8: The Practical Implementation
Unicode defines what numbers represent what characters, but we still need to store those numbers as bytes. That's where UTF (Unicode Transformation Format) comes in.
UTF-8 is the dominant encoding on the web (used by over 98% of websites). It's a variable-length encoding:
| Code Point Range | Bytes | Bit Pattern |
|---|---|---|
| U+0000 - U+007F | 1 | 0xxxxxxx |
| U+0080 - U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800 - U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 - U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF-8's brilliance:
- ASCII compatible: ASCII characters use the same single byte, so old ASCII files are valid UTF-8
- Self-synchronizing: You can find character boundaries by looking at byte patterns
- No null bytes: Safe for C strings (except for U+0000 itself)
- Efficient for Latin text: English text is the same size as ASCII
Example encodings:
'A' (U+0041) → 0x41 (1 byte)
'é' (U+00E9) → 0xC3 0xA9 (2 bytes)
'中' (U+4E2D) → 0xE4 0xB8 0xAD (3 bytes)
'😀' (U+1F600) → 0xF0 0x9F 0x98 0x80 (4 bytes)
UTF-8 vs UTF-16 vs UTF-32
| Encoding | Unit Size | Best For |
|---|---|---|
| UTF-8 | 1-4 bytes | Web, files, ASCII-heavy text |
| UTF-16 | 2-4 bytes | Windows internals, Java, JavaScript strings |
| UTF-32 | 4 bytes | Fixed-width processing (rare) |
UTF-16 uses 2 bytes for most common characters and 4 bytes (surrogate pairs) for others. It's used internally by Windows, Java, and JavaScript.
UTF-32 uses 4 bytes for everything—simple but wasteful. It's rarely used for storage or transmission.
Byte Order Mark (BOM)
Multi-byte encodings can store bytes in different orders:
- Big-endian: Most significant byte first
- Little-endian: Least significant byte first
The BOM is a special character (U+FEFF) at the start of a file that indicates the byte order:
| Encoding | BOM Bytes |
|---|---|
| UTF-8 | EF BB BF (optional, often problematic) |
| UTF-16 BE | FE FF |
| UTF-16 LE | FF FE |
UTF-8 BOM warning: While valid, the UTF-8 BOM can cause issues:
- Shell scripts may fail (interpreter can't read shebang)
- PHP may output characters before headers
- Concatenated files get BOM in the middle
Unless required (some Windows programs expect it), avoid UTF-8 BOM.
Fixing garbled text (mojibake)
Mojibake (from the Japanese for "character transformation") is what you see when bytes written in one encoding are read as if they were another. The original text is usually still there; it is just being displayed through the wrong lens. The classic examples all come from UTF-8 text being interpreted as Windows-1252 or ISO-8859-1:
| You see | You expected | What happened |
|---|---|---|
café |
café |
UTF-8 bytes C3 A9 read as Windows-1252 |
’ |
’ (curly apostrophe) |
UTF-8 bytes E2 80 99 read as Windows-1252 |
ü |
ü |
UTF-8 bytes C3 BC read as Windows-1252 |
 at the start of a file |
nothing | The UTF-8 byte order mark EF BB BF read as Windows-1252 |
caf� or caf? |
café |
Windows-1252 byte E9 read as UTF-8 (not a valid sequence, so a replacement character is shown) |
é |
é |
Double encoding: already-garbled é was encoded to UTF-8 again and misread again |
The telltale sign is a pair or triple of characters where you expected one, usually starting with Ã, â or Â.
Why it happens
- No declared encoding. If a file, HTTP response or database column does not say what encoding it uses, the reader guesses. Windows programs often default to the system code page (Windows-1252 in Western Europe and the Americas), while the web defaults to UTF-8, so the same file looks fine in one place and garbled in another.
- A wrong declaration. A
Content-Type: text/html; charset=ISO-8859-1header on a UTF-8 page, or a MySQL connection opened aslatin1againstutf8mb4tables, tells the reader to use the wrong lens. - Double encoding. Garbled text gets saved back to the database, then served as UTF-8, producing
éand similar. Each round trip adds another layer.
How to fix it
You reverse the misread: turn the garbled string back into the bytes it came from, then decode those bytes with the right encoding. In Python:
broken = "café"
fixed = broken.encode('cp1252').decode('utf-8') # 'café'
If encode('cp1252') raises an error on a character (Windows-1252 leaves five byte values undefined), try 'latin-1' instead. For double-encoded text, apply the fix twice. The ftfy library ("fixes text for you") automates all of this.
In JavaScript, TextDecoder can read raw bytes in either encoding, and can repair a string that was misread as Latin-1:
// Reading a file that is really Windows-1252
const text = new TextDecoder('windows-1252').decode(buffer);
// Repairing a string whose UTF-8 bytes were read as ISO-8859-1
const bytes = Uint8Array.from(broken, ch => ch.charCodeAt(0));
const fixed = new TextDecoder('utf-8').decode(bytes);
The repair works when every garbled character maps to a single byte (an ISO-8859-1 misread). If the garbage contains €, ™ or other characters from the 80–9F range of Windows-1252, the string was misread as Windows-1252 and those characters need mapping back to their byte values first. On the command line, iconv -f WINDOWS-1252 -t UTF-8 broken.txt > fixed.txt converts a whole file.
If you would rather not write code, paste the garbled text or upload the file into our Text Encoding Detector & Converter. It identifies the likely encoding and repairs the mojibake in the browser.
How to detect the encoding of a file
There is no metadata inside a plain text file that states its encoding, so detection works from the bytes:
- Byte order mark.
EF BB BFat the start means UTF-8,FF FEmeans UTF-16 little-endian,FE FFmeans UTF-16 big-endian. - Byte patterns. If every byte of
80or above forms a valid UTF-8 sequence (a lead byte fromC2toF4followed by the right number of80–BFcontinuation bytes), the file is almost certainly UTF-8. Lone high bytes such asE9orFCthat are not followed by continuation bytes point to a single-byte encoding. Bytes in the80–9Frange are typical of Windows-1252 (curly quotes,€,™) and unused in ISO-8859-1. Regular00bytes between ASCII letters suggest UTF-16. - Tools.
file -i name.txton Linux and macOS, Python'scharset-normalizerorchardet, and the encoding detector all apply these heuristics for you. Detection can never be certain: a short file containing only ASCII is valid in every one of these encodings.
Prevent it in the first place
Declare the encoding wherever text crosses a boundary:
Content-Type: text/html; charset=utf-8
<meta charset="utf-8">
# Always specify encoding when opening files
with open('file.txt', 'r', encoding='utf-8') as f:
content = f.read()
// Node.js
fs.readFileSync('file.txt', 'utf8');
Best Practices for Developers
- Use UTF-8 everywhere: Files, databases, APIs, HTML
- Declare encoding explicitly: Don't rely on defaults
- Validate input: Check for valid UTF-8 sequences
- Test with non-ASCII: Include accents, emoji, and various scripts in tests
- Store encoding metadata: Know what encoding your data uses
- Be careful with string length: "😀".length in JavaScript is 2 (UTF-16 code units), not 1
Database Configuration
-- MySQL
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- PostgreSQL
CREATE DATABASE mydb ENCODING 'UTF8';
Note: MySQL's utf8 is actually 3-byte UTF-8 (no emoji). Use utf8mb4 for full UTF-8 support.
Summary
- ASCII: 128 characters, foundation of text encoding
- Unicode: Universal character set with 150,000+ characters
- UTF-8: Variable-length encoding, web standard, ASCII-compatible
- Use UTF-8 for everything unless you have a specific reason not to
- Declare encoding explicitly to avoid mojibake
Understanding character encoding helps you build robust applications that work correctly with text from around the world.
Need to convert between encodings? Try our Unicode Encoder, or use the Text Encoding Detector & Converter to identify a file's encoding and repair garbled text.