.length Is Wrong & How to Get It Rightstr.length counts UTF-16 code units, not bytes and not even characters. If you're validating a string against a byte-based limit (a database column, an API payload cap), .length will pass strings that are actually too long the moment emoji, CJK, or accented text is involved.
A JavaScript string is stored as UTF-16. .length counts 16-bit code units. Most non-ASCII limits you actually hit — a database column, a network payload, an S3 key — are byte-based and almost always UTF-8. These numbers only agree for plain ASCII text.
| String | .length | Code points | UTF-8 bytes |
|---|---|---|---|
| "a" | 1 | 1 | 1 |
| "café" | 4 | 4 | 5 |
| "日本語" (3 CJK characters) | 3 | 3 | 9 |
| "😀" (one emoji) | 2 | 1 | 4 |
The emoji row is the one that trips people up: "😀".length is 2, not 1 — most emoji sit outside the Basic Multilingual Plane, so JavaScript represents them as a surrogate pair of two UTF-16 code units. That's still not the byte count: the same character is 4 bytes in UTF-8. All three numbers above were computed with Node's own Buffer.byteLength and Array.from, not estimated.
In the browser (or any modern JS runtime): use TextEncoder, a built-in with no dependency:
new TextEncoder().encode(str).length // UTF-8 byte length
In Node.js: Buffer.byteLength is the standard way, and defaults to UTF-8:
Buffer.byteLength(str, "utf8")
If you want to count actual characters rather than UTF-16 units — still not bytes, but closer to "how many characters is this" — spread the string, which iterates by code point instead of by code unit:
Array.from(str).length // code points, handles surrogate pairs correctly str.length // WRONG for this: counts surrogate pairs as 2
Note that even code point count isn't the same as what a person would call "how many characters does this look like" — a flag emoji or a family emoji built from a Zero-Width-Joiner sequence is visually one character but multiple code points. If you need that, you want grapheme clusters via Intl.Segmenter, which is what the counter below reports as "visual" count.
VARCHAR column, an API's payload cap) with str.length means any input containing emoji, CJK, or many accented characters can pass client-side validation and then get truncated or rejected by the actual byte-based check further down the stack — a class of bug that only shows up with non-English or emoji-containing input, so it's easy to miss in English-only testing.Paste text into the counter below to see all four numbers (bytes, .length, code points, grapheme count) at once, plus whether it fits common byte limits like MySQL's index limits or DynamoDB's item size cap.