Guides

What Is UTF-8? How Text Becomes Bytes

Marisol Vega··8 min read

I once lost the better part of a Tuesday to a single character. A customer named Zoë was showing up in our billing emails as Zoë, and the client was — reasonably — not thrilled. The data was fine. The database was fine. What was broken was that two systems quietly disagreed about what UTF-8 was, and neither one said so out loud.

So let's settle what UTF-8 actually is, because once you see how it works, that Zoë bug (and its cousin, the dreaded � box) stops being a mystery and turns into a five-minute fix. Here's the receipt from my Tuesday, and how to avoid ever spending one like it.

The problem: computers store bytes, but you typed letters

A computer doesn't store the letter A. It stores a number, and something has to agree on which number means which letter. For decades that agreement was ASCII: 128 characters — English letters, digits, punctuation — one byte each. Clean, simple, and completely out of room.

Because 128 slots don't cover é, or ß, or 中, or 😀, or the roughly 150,000 characters humanity actually writes with. The moment your app meets a name from outside plain English, ASCII shrugs. That was the wall — and for years everyone patched around it with a pile of regional encodings that didn't agree with each other, which is precisely the swamp my Tuesday crawled out of.

The fix, in two parts: Unicode and UTF-8

People smush these two together constantly (I did for years), so here's the clean split. Unicode is the giant catalog: it assigns every character a number called a code point — é is U+00E9, 中 is U+4E2D, 😀 is U+1F600. That's all Unicode does. It names things; it doesn't say how to store them.

UTF-8 is the storage plan — the rule for turning those code-point numbers into real bytes on disk or on the wire. And its central trick, the thing that made it win, is that it's variable-width: a character takes anywhere from 1 to 4 bytes depending on how big its code point is.

Why it's built like a phone number

Here's the analogy that finally made it stick for me. Think about dialing a phone. A local number is short. An international call needs a prefix — the + and a country code — and that prefix is a signal: this is a longer number, keep reading. You know the length from how it starts.

UTF-8 does exactly that with the first byte of each character:

  • Starts with a 0? It's a 1-byte character — and here's the elegant part: those are the exact same bytes as ASCII. Every old ASCII file is already valid UTF-8, which is the whole reason UTF-8 could take over without breaking the existing web.
  • Starts with 110? This character is 2 bytes.
  • Starts with 1110? Three bytes. 11110? Four.
  • And every follow-on byte starts with 10, so a decoder can always tell a "continuation" byte from the start of a fresh character.

That's what self-describing means: UTF-8 never has to guess where one character ends and the next begins, because each character announces its own length up front — like a phone number that tells you how many more digits are coming. As the lore goes, Ken Thompson and Rob Pike sketched this design on a New Jersey diner placemat in 1992; placemat or not, that self-describing structure is why it beat every rival. You can watch a code point turn into its bytes with the Unicode Encoder, and reverse the trip with the Unicode Decoder.

Where it breaks: mojibake and the � box

Now my Tuesday makes sense. Zoë's ë is code point U+00EB, which UTF-8 stores as two bytes, C3 AB. Our database handed those two bytes to an email system that assumed they were Latin-1 — one byte per character — instead of UTF-8. Latin-1 reads C3 as à and AB as «, and the name mangles. Same phenomenon, different flavor, every time you see é, ’, or Zoë: the bytes are correct, they're just being read with the wrong encoding. Nothing is corrupted. Two systems simply disagree.

The � replacement character is the other failure mode. That's what a UTF-8 decoder shows when it hits bytes that aren't valid UTF-8 at all — often a truncated multi-byte character (an emoji sliced in half by a length limit) or genuinely mismatched data. When you're inspecting raw bytes to debug this, a Hex to Text converter is your friend; to see how any character breaks down to individual bits, run it through the Text to Binary tool — the same bytes our guide to reading binary walks through by hand.

The fix that would've saved my Tuesday

Almost every mojibake bug is a declaration bug. Some layer is assuming an encoding instead of being told one. Close the gaps:

  • Put <meta charset="utf-8"> near the top of your HTML <head>.
  • Send charset=utf-8 in your Content-Type response header from the server.
  • Make sure your database and its columns are UTF-8 — in MySQL that means utf8mb4, not the old utf8, which (infuriatingly) can't even store emoji.
  • Read and write files as UTF-8 explicitly, instead of trusting the operating system's default.

The pattern never changes: state the encoding at every boundary so nothing downstream has to guess. Guessing is where names like Zoë are born.

The takeaway

UTF-8 is the self-describing, variable-width way computers store every character Unicode defines — one byte for the old ASCII set, up to four for everything else, each character announcing its own length like a phone number with a country code. When text goes weird, the bytes usually aren't broken; some layer just read them with the wrong encoding. Declare UTF-8 everywhere, and you'll spend your Tuesdays on better bugs than mine. Poke at how characters become bytes in the Unicode Encoder — it's the fastest way to make this click.

Try the tools

Frequently Asked Questions

What is UTF-8 in simple terms?

UTF-8 is the standard way computers store text as bytes. It gives every character a number (via Unicode) and then encodes that number in 1 to 4 bytes — one byte for plain English letters, more for accented, non-Latin, and emoji characters. It's the default encoding of the modern web.

What's the difference between UTF-8 and Unicode?

Unicode is the catalog that assigns every character a number (a code point), like é = U+00E9. UTF-8 is one way to turn those numbers into actual bytes for storage or transmission. Unicode says what the characters are; UTF-8 says how to store them.

Why do I see weird characters like é instead of é?

That's mojibake, and it means correct UTF-8 bytes are being read with the wrong encoding (often Latin-1). The é is stored as two UTF-8 bytes; a decoder that assumes one byte per character reads them as two separate symbols. The data is fine — the reader guessed wrong.

Is UTF-8 the same as ASCII?

Not the same, but compatible. The first 128 UTF-8 characters use a single byte with the exact same values as ASCII, so every ASCII file is already valid UTF-8. UTF-8 then extends beyond ASCII's 128-character ceiling to cover every Unicode character using additional bytes.

How many bytes does a UTF-8 character use?

Between 1 and 4. ASCII characters (English letters, digits, common punctuation) take 1 byte; most accented and Greek/Cyrillic letters take 2; most other scripts including Chinese take 3; and emoji and rarer characters take 4.

MV

Marisol Vega writes for CodeUtilityKit, where the team builds free, privacy-first developer tools that run entirely in your browser. Every guide is written and reviewed by developers who use these tools daily.