Counting Characters in Unicode Text
AI-assisted article. Reviewed by Codebuff automated editorial review on 2026-11-04. Examples are illustrative, not real user data.
Character limits look simple until the text contains an emoji, an accent typed as a combining mark, or a flag. The same string can report three different lengths depending on whether you count bytes, UTF-16 code units or user-perceived characters. Being refused at the limit is annoying; having a paste silently cut in the middle of a code block is worse.
Three numbers, one string
JavaScript exposes the UTF-16 length as a property, the code-point count through iteration, and the byte count through a text encoder. None of them is wrong, and none of them matches what a reader would call the number of characters. The differences appear as soon as the text leaves the basic Latin range.
This short string contains a skin-toned emoji, a flag built from two regional indicators, and a family joined with zero-width joiners. It reports 18, 11 and 36 depending on the measure, while a reader sees three symbols.
const text = '👍🏽 🇳🇱 👩👩👧';
text.length; // 18 UTF-16 code units
[...text].length; // 11 code points
new TextEncoder().encode(text).length; // 36 UTF-8 bytes
[...new Intl.Segmenter('en', { granularity: 'grapheme' }).segment(text)].length; // 3
Where limits bite
A limit described as characters is usually checked against a language-level string length, which in JavaScript means UTF-16 code units. An emoji outside the basic plane then spends two units, and a flag spends four. A note full of emoji can reach a limit long before it looks long.
A limit described in bytes behaves differently again. UTF-8 uses one byte for plain ASCII, two or three for most accented letters, and four for many emoji. A file that is comfortably under a character limit can still be over a byte-based upload cap.
Truncation breaks syntax, not just meaning
Cutting a UTF-16 string at an arbitrary index can split a surrogate pair, leaving a replacement character where an emoji used to be. That is cosmetic. Cutting JSON, YAML or code at an arbitrary index is not: the remaining text no longer parses, and the reader sees a syntax error instead of the bug.
Trim by structure before trimming by length. Remove a whole request from a log, a whole object from an array, or a whole section from a document, and leave the surrounding syntax intact. If the cut has to land inside text, cut on a line boundary and replace the removed part with a visible marker.
Measure before you share
Count in the same runtime that will send the text. A browser using JavaScript counts UTF-16 units; a shell tool counting bytes will disagree. When the number is close to the limit, split the content into two pastes by structure and label them, which is more useful than a single file that had to be cut.
Keep the original after splitting. The reduced copy is for the reader, and the original is what you will need when the next question goes deeper than the trimmed version.
Sources and editorial review
Prepared with AI assistance. The counts in the example follow from the Unicode encoding rules and the code shown, and were checked by hand rather than by running the snippet in a browser. The article does not claim a specific unit for the site's published limits, because that belongs in the product documentation, not in an inference.