Counting Characters Against a Limit Someone Else Set
Published 8/10/2026 · 13 min read · Text & language tools
Daniel Okonkwo — Front-end developer and tech writer at Allin
Web performance · File formats
Checked against 4 sources
"Character" is not one thing, and whoever wrote the limit decided which one without telling you. The frequency counter on this page walks the string with a code-point iterator, so it counts Unicode code points. Paste the family emoji into it and it returns five rows summing to seven: four rows for the four people, and one row for something that renders as nothing at all — the zero-width joiner, with a count of 3. That same string is 1 grapheme cluster, 7 code points, 11 UTF-16 code units and 25 UTF-8 bytes. Those four numbers are what four different systems call its length, so a box that says 500 characters admits 500 of them if it counts graphemes, 71 if it counts code points, 45 if it uses JavaScript's .length or Java's .length(), and 20 if the column behind it is 500 bytes. The tool prints no total; you add the count column yourself, and its defaults skip whitespace and fold case, which is why "Merci beaucoup !" gives 11 rows summing to 14 rather than 16 characters. SMS is stricter. The transfer protocol carries 140 octets, which is 160 characters of the GSM 7-bit alphabet or 70 in UCS-2, dropping to 153 and 67 once messages are joined. One character outside the 7-bit table flips the whole message: a curly apostrophe does it, any emoji does it, and so do the Spanish á í ó ú and the Portuguese ã õ, which the default table simply does not contain. Meta descriptions are not counted at all — snippets are truncated by rendered width, not by character count.
One emoji is 1 character, or 7, or 11, or 25, depending on who is counting. Which one your form, your database and your SMS gateway mean — and a one-paste test that tells you which you are facing.
Four numbers for one emoji
Take the family emoji made of a man, a woman, a girl and a boy. It is one thing on screen, one press of Backspace deletes it, and one text cursor step moves past it. Underneath it is four people-emoji separated by three copies of an invisible control character, U+200D, the zero-width joiner. Measured four ways, that string is 1 grapheme cluster, 7 code points, 11 UTF-16 code units and 25 UTF-8 bytes. Nothing is wrong with any of those numbers; they answer four different questions.
The grapheme cluster is the user-perceived character, and Unicode defines the boundaries between them in a technical annex, UAX #29, precisely because "what a reader thinks of as one character" is not obvious enough to leave to each program. The code point is the numbered entry in the Unicode catalogue. The UTF-16 code unit is a sixteen-bit slot, and anything above U+FFFF needs two of them — that is the number JavaScript, Java and C# report when you ask for a string's length. The UTF-8 byte is what actually travels down the wire and sits in a database column.
What this tool counts, exactly
The frequency counter loops over the input with a for-of loop, which in JavaScript walks code points rather than sixteen-bit units. So it counts code points, and you can see it do so. Paste the family emoji with the default settings and the output is five rows: a blank-looking row with the count 3, then four rows with the count 1 each. The blank row is the zero-width joiner. Sum the column and you get 7, which is the code-point count, not the 1 you would call it if asked how many characters you had pasted.
Two defaults change the arithmetic before you see it. Skip whitespace is on, so spaces never appear in the table: "Merci beaucoup !" is 16 characters but produces 11 rows summing to 14, because the two spaces are dropped. Ignore case is on too, and it folds pairs together — which is usually what you want, and occasionally produces something strange. The Turkish capital İ lowercases to a lowercase i plus a separate combining dot, so the table gains a row whose key is two code points long. The tool never claimed the row key was a single character; it is whatever the lowercase mapping produced.
And it prints no total. The output is a two-column table of character and count, sorted by count then alphabetically, and the number you probably came for is the sum of the second column. If you want the four counts side by side without arithmetic, the UTF-8 byte counter on this site prints graphemes, code points, UTF-8 bytes, UTF-16 units, words and lines in one go — it exists because those numbers disagree, and its own comment says so.
The one-paste test that tells you which counter you are facing
Paste one family emoji into the field with the counter, and read the counter. If it says 1, it counts grapheme clusters. If it says 7, it counts code points. If it says 11, it uses the language's native string length, which means UTF-16 code units — JavaScript, Java or C#. If it says 25, it is counting UTF-8 bytes, and the limit is really a byte limit. Four possible answers, four different systems behind the box, and the test takes one paste.
There is a second half to the test, and it matters more: the counter you see in the browser is not necessarily the one that decides. A front-end counter is almost always JavaScript's .length, while the rejection you get on submit comes from a server, a column definition or a downstream API with its own idea. If the box lets you type 500 and the save fails at 480, the two ends are counting differently and the shorter one wins. Type the text, save it, then reload the page and look at what came back — truncation is easier to see than to predict.
SMS: 160 characters, or 70, and one apostrophe decides
The short message service carries up to 140 octets of user data. Pack that with the GSM 7-bit default alphabet and you fit 160 characters; encode it as UCS-2, two bytes per unit, and you fit 70. When a message is too long it is split, and each part gives up room to a small header that says which part it is: that leaves 153 seven-bit characters or 67 UCS-2 units per segment. Nothing about this is negotiable, and every bulk-SMS price list is built on it.
The interesting part is what is in that 7-bit table, because a single character outside it flips the whole message to UCS-2 and more than halves the capacity. The table does hold a generous set of accents — è é ù ì ò à ä ö ü ñ å æ ø ß, plus Ä Ö Ü Ñ É Å Æ Ø Ç — along with ¡ ¿ § and a handful of currency symbols. It does not hold á, í, ó or ú, and it does not hold ã or õ. So a Spanish message containing "está" or "aquí", or a Portuguese one containing "não", is a 70-character message, not a 160-character one, and nothing on the screen tells the sender.
The specification anticipated this and defines national language tables — Spanish gets a single-shift table, Portuguese gets both a single-shift and a locking-shift one. A single-shift character costs two of your seven-bit slots instead of one, and both ends have to implement the mechanism. In practice most gateways do not try, and drop to UCS-2 instead. The same escape mechanism is why a small set of common symbols already cost two slots each in plain messages: the caret, the braces, the square brackets, the backslash, the tilde, the vertical bar and the euro sign all live in the extension table, not the main one. And the typographic apostrophe your word processor inserted when you typed an ordinary one is not in either table.
The meta description is measured in pixels, not characters
Every SEO checklist gives a character range for the meta description. Google's own documentation gives none: it says there is no limit on how long a description can be, that the snippet is generated from the page and sometimes from the description, and that snippets are trimmed to fit the result. Fitting is a matter of rendered width, so a description full of wide letters is cut sooner than one of the same character count made of narrow ones, and the mobile result gets less room than the desktop one.
The practical consequence is not to abandon a character budget but to stop treating it as a rule. Put the part that must survive in the first half of the sentence, then check the real result rather than the count. And be aware that Google frequently rewrites the description entirely when it judges the page content answers the query better — a truncated description you never wrote is a different problem from a truncated one you did.
Two spellings of the same word, two lengths
There is a last trap that has nothing to do with emoji. The letter é can be one code point, U+00E9, or two: a plain e followed by a combining acute accent. They look identical, they mean the same thing, and they have different lengths — 1 code point and 2 bytes against 2 code points and 3 bytes. Most keyboards produce the first; some operating systems, some scanners and a great deal of copied-and-pasted text produce the second. Run the decomposed form through the frequency counter and it comes back as two rows, one of which is a bare accent sitting on nothing.
If a length check is failing on text that looks the right length, this is the first thing to test. Normalising to the composed form before counting fixes it, and it is one call in every language that ships a Unicode library. Do it before you count, before you store, and before you compare two strings for equality — the same word in two normalisation forms is not equal by any byte comparison.
| Unit of counting | The family emoji counts as | Where you meet it | How many fit under "500 characters" |
|---|---|---|---|
| Grapheme clusters (UAX #29) | 1 | Swift's String.count; one Backspace; the first row of the UTF-8 byte counter | 500 |
| Unicode code points | 7 — four people plus three joiners | Python 3's len(); Go runes; the character frequency counter on this page | 71 |
| UTF-16 code units | 11 | JavaScript .length, Java .length(), C# .Length — most in-browser form counters | 45 |
| UTF-8 bytes | 25 | Go's len(); a column or a header measured in bytes | 20 |
| UCS-2 units in one SMS segment | 11 | A single 70-unit segment, or 67 once messages are joined | 6 per segment — the seventh starts another message |
Frequently asked questions
- Why does the frequency table have a blank row?
- Because a character that renders as nothing is still a character, and the counter reports it honestly. In the family emoji that row is U+200D, the zero-width joiner, which appears three times: it is what glues the four people into one picture. Variation selectors behave the same way — the little U+FE0F that turns a monochrome symbol into a colour emoji is invisible and counted. A blank row with a number beside it is the tool telling you that your text contains something you cannot see, which is exactly what you want to know before pasting it into a field with a limit.
- Does one emoji really halve my SMS?
- More than halve it. A message is encoded in one alphabet from end to end, so a single character outside the GSM 7-bit table forces the whole message into UCS-2, and capacity falls from 160 characters to 70. It is worse for emoji specifically: anything above U+FFFF takes two UCS-2 units, so a plain smiling face costs two of your 70 and the family emoji costs eleven. Six of those emoji and one word will not fit in a single segment. If your bulk-SMS bill jumped without the copy getting longer, look for a curly apostrophe or an accent the table does not carry before you look anywhere else.
- My form says 500 characters. Which 500 does it mean?
- Test it rather than guess. Paste one family emoji and read the counter: 1 means grapheme clusters, 7 means code points, 11 means UTF-16 code units, 25 means UTF-8 bytes. In plain ASCII all four agree, which is why the difference only shows up when a real user pastes a name with an accent or an emoji into the box. If you own the form, count grapheme clusters for the user-facing number and validate against whatever the storage layer really enforces, then set the visible limit to the smaller of the two. If you do not own it, assume the smallest plausible answer and leave slack.
- Is a database VARCHAR(500) five hundred bytes or five hundred characters?
- In the mainstream relational databases the declared length is in characters, not bytes — but the limits around it are in bytes, and that is where people get caught. A row has a maximum size in bytes, an index has a maximum key size in bytes, and a four-byte-per-character encoding multiplies both. So the column accepts your 500 characters and the index on it refuses to be created. Byte limits are more common outside the database: HTTP header values, message-queue payloads, object-storage keys and plenty of third-party APIs are all specified in bytes. When the documentation says bytes, count bytes.
- How many characters should a meta description be?
- There is no documented number, and any specific one you have been given was measured from search results rather than published as a rule. Google's guidance is that descriptions have no length limit, that snippets are drawn from the page as well as the description, and that they are trimmed to fit — fitting being a matter of rendered width in a layout that differs between phone and desktop. The useful habit is to front-load: put the claim, the number or the differentiator in the first clause, and treat everything after it as expendable. Then look at the actual result in a real search and adjust from what you see.
Articles you may find interesting
All guides →Related tools
This describes what these text tools do today, checked by running their own functions against the exact inputs quoted here. Where a tool gets a case wrong, that is written down rather than smoothed over, because a tool you cannot predict is worse than one you know the limits of. Nothing here is a rule any tool is obliged to follow: capitalisation, character counting and line numbering are conventions, and the conventions differ by language, by style guide and by whichever program is on the other end. Before running any of these over text you cannot retype, run it over a copy and compare both ends.
Sources
- Unicode Consortium — UAX #29, Unicode Text Segmentation — the grapheme cluster boundary rules that define a user-perceived character, including the treatment of zero-width joiners, variation selectors and emoji modifier sequences
- ETSI / 3GPP — TS 123 038 (3GPP TS 23.038) — the GSM 7-bit default alphabet table in clause 6.2.1, its escape-driven extension table in 6.2.1.1 (which holds the caret, braces, brackets, backslash, tilde, vertical bar and euro sign), and the national language single- and locking-shift tables for Spanish and Portuguese in 6.2.1.2 and Annex A
- ETSI / 3GPP — TS 123 040 (3GPP TS 23.040) — the transfer of short messages: the user data of an SM MT or SM MO carries up to 140 octets, which is what yields 160 seven-bit characters or 70 UCS-2 units per single message
- Google Search Central — Control your snippets in search results — no documented character limit for a description; snippets are generated from the page and from the description and are trimmed to fit the result
Spotted a mistake in this article?