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.