
Type café into a box that caps input at 4 characters, and it can still come back rejected. The word looks like 4 characters, but depending on how the é was typed some tools count 5, and converting it to bytes for storage or a network request gives a third number. Characters, code points, and bytes are three separate counts, and confusing them is how a limit like VARCHAR(n) ends up failing in places nobody thought to check.
Character count and byte count measure two different things
Character count is what you'd get counting by eye: "café" is 4, "hello" is 5. Byte count is how much storage or network payload the same text needs, and that depends entirely on which encoding turned those characters into bytes.
For plain ASCII text the two numbers agree, because every character there costs 1 byte. Add anything outside that range — an accented letter, a CJK character, an emoji — and they come apart. So a word processor's status bar and a database error message can quote two different figures for the same paragraph: one is counting units a reader would recognize, the other is counting whatever the storage layer wrote to disk.
UTF-8 keeps ASCII at 1 byte, but accented characters can cost 2 bytes
UTF-8 is a variable-length encoding: 1 byte for every ASCII character (A–Z, 0–9, common punctuation), more for everything outside that range. An accented Latin letter like é or ś already sits outside ASCII, so UTF-8 spends 2 bytes on it. Nothing about the letter is unusual; its code point simply sits past U+007F.
That design is what made UTF-8 the default for the web and most modern APIs: plain English text stays compact, and every other script pays only for the bytes it needs. Here are the common cases, from a single ASCII digit up to a four-byte emoji.
| Character type | Example | UTF-8 bytes |
|---|---|---|
| ASCII letter or digit | A, 1 | 1 byte |
| Accented Latin letter | é, ś | 2 bytes |
| CJK character | 語, 中 | 3 bytes |
| Emoji | 😀 | 4 bytes |
"that subset only requires a single byte for processing and storage in UTF-8"/"U+015B LATIN SMALL LETTER S WITH ACUTE would be encoded as two bytes: C5 9B."(General questions section)View official source →
A single accented letter can be a two-code-point grapheme cluster
Byte count is not the only surprise here. Many accented letters can be stored as two code points rather than one: a plain letter followed by a separate combining accent mark, instead of a single precomposed character. What looks like one letter on screen — a grapheme — can be a short sequence underneath.
Hand one of those sequences to a length function that counts code points, and it will report more characters than a person would. The layers underneath are distinct: a character is what someone sees, a code point is what Unicode assigns a number to, a byte is what gets stored. All three can disagree about the same piece of text, which is why a spec that says "255 characters" and stops there is already ambiguous.
"many times what users perceive as a single unit, an emoji for example, is represented as a combining or other character sequence"(Do APIs need to use 32-bit characters? section)View official source →
UTF-16 counts most characters as one unit, and emoji as two
UTF-16, used internally by JavaScript, Java, and Windows, stores characters in the Basic Multilingual Plane as a single 16-bit code unit. Characters outside it, including most emoji, need a pair of code units called a surrogate pair.
JavaScript's .length counts those 16-bit code units rather than characters, so a string with an emoji in it can report a .length one higher than the number of characters a person would count. For plain text the gap never shows; it appears the moment a supplementary character enters the string.
"BMP characters require one 16-bit code unit to process or store, whereas supplementary characters require two"(Basic Multilingual Plane section)View official source →
A VARCHAR character limit means something different per database
A column declared VARCHAR(255) does not enforce 255 bytes everywhere. It enforces 255 of whatever unit that particular database happens to count.
In MySQL and in PostgreSQL, the number in VARCHAR(n) is a character-count limit, so the column always accepts a full 255 characters. Feed it accented or CJK text and those same 255 characters can add up to far more than 255 bytes once UTF-8 has stored them.
SQLite goes further: it does not enforce the number at all. The (n) parses fine, but a VARCHAR(10) column will still store a much longer string without complaint.
| Database | What VARCHAR(n) limits |
|---|---|
| MySQL | n characters |
| PostgreSQL | n characters |
| SQLite | Not enforced — the length modifier is accepted but ignored |
So check this per project rather than carrying an assumption from one database to the next. Migrate from a database that ignores the limit to one that enforces it in bytes, and rows that passed validation for years can start failing.
Where byte count decides pass or fail
The limit that gets enforced is usually the byte count. Column widths declared in bytes, HTTP header and URL length caps, filename restrictions on some filesystems, the payload ceilings of network protocols and message queues: these cap bytes, not characters. When a form silently truncates your input, or an API rejects a request for no stated reason, a byte ceiling is often what you've hit — and it is nowhere in the interface.
Bytes are also the base that file size units build on: 1,024 of them make a kilobyte, and 1,024 kilobytes make a megabyte. See File size units explained: how many GB in a TB? for the full ladder and the conversion table.
Check character count and byte count together
sakuttoAI's character counter reports UTF-8 bytes and UTF-16 bytes for the same input at once, so the character total sits right beside both and you never retype the text into a second tool. There is also a Shift-JIS byte estimate, which is aimed at legacy Japanese-text workflows rather than the encodings covered above.
The UTF-8 figure comes straight from the browser's own text-encoding function, so it matches what goes over the wire. Check that number before you trust any byte-based limit.
Summary
Three numbers are in play — characters, code points, bytes — and only the last of them moves when the encoding changes. UTF-8 keeps ASCII at 1 byte but can spend 2 on an accented Latin letter, and VARCHAR(n) enforces a character limit in MySQL and PostgreSQL while SQLite lets it slide. Check the encoding and check the database before you assume any of these figures carries from one system to the next.
FAQ
- How many characters does "café" have?
- It depends on how the é was typed. As a single precomposed code point, it reads as 4 in most tools. Typed as a plain \"e\" plus a separate combining accent mark, it comes out as 5 in some of them, because they are counting code points rather than the grapheme you see.
- Does VARCHAR(255) mean 255 bytes?
- No — in MySQL and PostgreSQL, `VARCHAR(255)` is a character-count limit. It allows exactly 255 characters however many bytes each of them costs in UTF-8. SQLite does not enforce the number at all, so a longer string still goes in.
- What does JavaScript's .length actually count?
- It counts UTF-16 code units rather than characters. For text made only of Basic Multilingual Plane characters the two figures match, but add an emoji or another supplementary character and `.length` comes out one higher than the count a person would give.
- How many bytes does an emoji take in UTF-8?
- Most emoji sit outside the Basic Multilingual Plane, in the range UTF-8 encodes as 4 bytes each. Skin-tone and other modifiers add a further 4-byte sequence apiece, so one visible emoji can total well over 4 bytes.



