This Emoji Is One Character, Seven Characters and Eleven Characters
You paste something into a field with a 280-character limit and it says you are over by nine, but you counted 271. Neither of you is wrong. There are four different reasonable answers to "how many characters is this", they disagree most on exactly the text people paste, and knowing which one a system uses tells you why it rejected you.

Six short strings, counted four different ways:
| text | graphemes | code points | UTF-16 units | UTF-8 bytes |
|---|---|---|---|---|
ß | 1 | 1 | 1 | 2 |
é(single code point) | 1 | 1 | 1 | 2 |
é(e + combining accent) | 1 | 2 | 2 | 3 |
| 👍 | 1 | 1 | 2 | 4 |
| 👍🏽 (with skin tone) | 1 | 2 | 4 | 8 |
| 👨👩👧👦 (family) | 1 | 7 | 11 | 25 |
The family emoji is the extreme case and it is not exotic — it is on every phone
keyboard. One thing on screen, eleven "characters" to a JavaScript .length, and
twenty-five bytes in a database.
The four counts, and who uses which
Graphemes are what a person means by "character": one visible unit, however many code points went into it. This is what a text cursor moves over and what pressing backspace deletes. It is also the hardest to compute — the rules are a Unicode annex, they change between versions, and most languages need a library.
Code points are Unicode's own units. éwritten as eplus a combining acute
is two code points that render as one grapheme. Emoji sequences are several code
points joined by an invisible zero-width joiner. Python's len()counts these.
UTF-16 units are what JavaScript's .lengthreturns, and Java's, and C#'s.
Anything outside the first 65,536 code points — which is all emoji, and a great deal
of CJK — takes two units instead of one. This is why 👍 costs two and the family
costs eleven.
Bytes are what storage and network limits count. In UTF-8, ASCII is one byte, Latin accents two, most CJK three, emoji four.
Where this bites
Twitter-style limits. Platforms that advertise a character limit usually count graphemes or code points, and the box in front of you may count UTF-16 units. The number of "you are over the limit" bugs caused by this is large.
Database columns. VARCHAR(255)means 255 bytes in some databases and 255
characters in others, and in MySQL the character interpretation depends on the
column's charset — which is why the older utf8charset, which is really three-byte
UTF-8, silently rejects emoji while utf8mb4accepts them. A field that worked for
years starts failing when someone types a 👍.
Truncation. Cutting a string at byte 100 can land in the middle of a multi-byte
character and produce invalid UTF-8 — the classic �at the end of a truncated
snippet. Cutting at UTF-16 unit 100 can split a surrogate pair, or split a family
emoji into a man, a woman and two children.
Passwords and validation. A minimum length in code points and a maximum in bytes can be contradictory for a user typing in a non-Latin script.
The rules that keep you out of trouble
Store bytes, display graphemes, validate in whatever the user sees. A limit the user cannot verify by looking is a limit they will hit by surprise.
Never truncate by byte or by UTF-16 index without checking the boundary. Most languages have a way to iterate graphemes or at least code points; use it. If you truncate for a preview, truncating at a word boundary is better anyway.
Normalize before comparing or deduplicating. caféwritten two ways is two
different strings until you normalize them — which is its own
problem.
Size database columns for the worst case. If a field can hold user text it can hold emoji, and emoji are four bytes each. A 20-character name field is 80 bytes if you want it to work for everyone.
Counting it yourself
In a browser console:
const s = "👨👩👧👦"
s.length // 11 — UTF-16 units
[...s].length // 7 — code points
[...new Intl.Segmenter("en", {granularity:"grapheme"}).segment(s)].length // 1
new TextEncoder().encode(s).length // 25 — UTF-8 bytes
Four lines, four different numbers, one emoji. When a system tells you a length you disagree with, one of these is the number it is using, and the gap between them is almost always where the confusion lives.
Why the counts diverge at all
The gap between these numbers is a history of how text encoding was fixed twice.
Unicode originally assumed 65,536 code points would be enough for every script, and
16 bits per character became the design of Java, JavaScript, Windows and several
others. When it turned out not to be enough — historic scripts, rare CJK, and later
emoji all live above that ceiling — those systems could not widen their character
type without breaking every program written for them. The solution was surrogate
pairs: encode a high code point as two 16-bit units that are individually
meaningless. That is why .lengthreturns 2 for a single emoji, and it is a
permanent artifact of a decision made in 1991.
UTF-8 avoided that problem by being variable-width from the start, at the cost of a character taking between one and four bytes. Graphemes then sit on top of everything as the layer that describes what a reader sees, which needs its own rules because combining marks and joiners were always going to produce visible units made of several code points.
So the four numbers are not four opinions about the same thing. They are four genuinely different quantities, each correct for a different purpose, and the only error is using one where another was meant.