Calculate String Length

Character count and byte count side by side, they differ the moment you use an emoji, plus how much room is left in a post or a bio.

Code units, code points, and what JavaScript counts

Ask how long a string is and there are at least four defensible answers. For plain English they agree, which is exactly why the difference catches people out only once real user input arrives.

JavaScript's `.length` counts UTF-16 code units, not characters. Characters outside the Basic Multilingual Plane (every emoji, every mathematical alphanumeric of the sort this site generates, most historic scripts) are stored as a surrogate pair and count as two. So `'😀'.length` is 2, and a bio that looks like twenty characters can report as thirty.

Counting code points instead treats the surrogate pair as the single character it represents. Spreading the string with `[..str]` or iterating it with `for..of` does this, because both follow the string's own iterator, which is code-point aware. That is the count this page reports as Characters, and it is the number that matches what a person sees for most text.

Where even code points are wrong

Code points are still not what a reader would call a character. A grapheme cluster is: the unit that renders as one visible mark and moves as one under a cursor key.

The family emoji is the standard demonstration. It is four people joined by zero-width joiners, seven code points, eleven UTF-16 code units, one visible glyph. A flag is two regional indicator letters. An emoji with a skin-tone modifier is two code points. An accented letter written in decomposed form is a base letter plus a combining mark. In every case the reader sees one character and the naive count disagrees.

The correct tool is `Intl.Segmenter` with granularity `grapheme`, which is in every current browser. This page reports code points rather than graphemes because that is what the platforms below count, but if you are writing a truncation function, graphemes are the unit that stops you slicing a family emoji in half.

Bytes, and why the database disagrees with the form

UTF-8 encodes a code point in one to four bytes. ASCII is one, Latin letters with accents and most European scripts are two, the bulk of the BMP including CJK is three, and everything above it (emoji, mathematical alphanumerics) is four.

This is where a field silently rejects text that looked short enough. A column declared as 255 characters is very often 255 bytes underneath, and the two only coincide for pure ASCII. A bio of sixty emoji is sixty characters and two hundred and forty bytes. MySQL's older `utf8` encoding compounded this by being three-byte-limited, so it could not store emoji at all and needed `utf8mb4`; a great many legacy schemas still carry the wrong one.

The byte count on this page is the true UTF-8 length, measured with `TextEncoder`. When a field rejects text that the character count says should fit, this is the number to check.

What the platform counters actually mean

The two room-left counters cover the limits people hit most: 280 for a post and 150 for an Instagram bio. Both count down as you type, which is more useful than a running total when you are editing to fit.

Platforms do not all count the same way, and the differences are not documented consistently. X counts most characters as one but weights some ranges as two, and replaces every link with a fixed 23-character cost regardless of the URL's real length. Instagram counts closer to code points. Some systems count UTF-16 units, which penalises emoji at double rate.

The practical advice is to leave a few characters of headroom rather than aiming for exactly zero remaining, particularly in any field that truncates silently rather than warning you.

Counting words and sentences

The word count splits on runs of whitespace, which is the standard approach and is right for European languages. It is wrong for Chinese, Japanese and Thai, which do not delimit words with spaces. A Chinese paragraph will report as one or two words. Proper segmentation there needs `Intl.Segmenter` with word granularity and a dictionary, which is beyond what a live counter should attempt.

Sentence counting looks for terminal punctuation, so it over-counts abbreviations, Dr. Smith reads as two sentences, and under-counts text that runs on without full stops. Treat it as an approximation for readability rather than a precise figure.

Everything runs in your browser. Nothing you paste is transmitted, which matters if you are measuring text you would not want to send to a server.

Truncating without breaking the text

Counting is half the problem; cutting is the other half, and it is where naive code does visible damage.

`str.slice(0, 150)` cuts on UTF-16 code units. If position 150 falls in the middle of a surrogate pair it splits an emoji into two lone surrogates, which render as replacement squares and are invalid UTF-8 the moment you try to store them. Cutting on code points with `[..str].slice(0, 150)` fixes that specific break but still severs a grapheme cluster: a family emoji loses its trailing members and silently becomes a different, shorter emoji, and an accented letter written in decomposed form loses its accent.

The correct unit for truncation is the grapheme cluster, via `Intl.Segmenter` with grapheme granularity. Segment, take the first n, join them back. That is the only approach that guarantees the cut lands where a cursor could.

Two more details matter in practice. If you append an ellipsis, count it against the budget. A 150-character limit with an ellipsis leaves 149 characters of text, and trailing whitespace should be trimmed before the ellipsis so you do not produce a space before the dots. And cut on a word boundary where the text is prose: severing mid-word is more jarring than losing a word, and `Intl.Segmenter` with word granularity gives you that too.

Frequently asked questions

Why is my byte count higher than my character count?

Because UTF-8 uses one to four bytes per character. Accented Latin letters take two, most CJK three, and emoji four. The two counts only agree for pure ASCII.

Which number does a database limit use?

Usually bytes, even when the column is described in characters. If a field rejects text that looks short enough, check the byte count, and check whether the column is MySQL `utf8` rather than `utf8mb4`, which cannot store emoji at all.

Why does JavaScript say my emoji is two characters?

Because `.length` counts UTF-16 code units, and characters outside the Basic Multilingual Plane are stored as a surrogate pair. This page counts code points instead, which is why it reports one.

Is one emoji always one character?

No. A flag is two code points, a skin-tone variant is two, and the family emoji is seven joined by zero-width joiners. They render as one grapheme cluster but count as several.

Do these counters match the platform exactly?

Close, but not exactly. X weights some character ranges as two and charges every link a flat 23 characters. Leave a little headroom rather than aiming for zero remaining.

Does the word count work for Chinese or Japanese?

No. It splits on whitespace, and those languages do not delimit words with spaces, so a paragraph reports as one or two words. Accurate segmentation there needs a dictionary-based approach.

How do I truncate text without breaking emoji?

Segment with `Intl.Segmenter` at grapheme cluster granularity and take the first n clusters. Slicing by code unit splits surrogate pairs into replacement squares; slicing by code point severs joined sequences like the family emoji.