What's the Length of a Thumbs Up
Here's a fun one to try in your browser's console. Type "👍".length and hit enter. You'll get 2. Now try "👨👩👧".length. You'll get 8. One little family picture, eight units of length. If that makes you uneasy, good. Text is a lot weirder than it looks, and most of the bugs come from assuming one visible character equals one of something.
The foundation is Unicode, which is basically a giant numbered list of characters. Every letter, symbol and emoji gets a number called a code point. "A" is U+0041. "é" is U+00E9. 👍 is U+1F44D. There's room for over a million of them, and they cover pretty much every writing system people use.
But a number isn't bytes yet. You need an encoding to turn code points into bytes for storage, and that's where UTF-8 comes in. It uses one byte for basic English letters (which keeps it compatible with plain old ASCII) and up to four bytes for everything else. It's the default for the web and the one you should use for basically everything. Some languages, including JavaScript and Java, use UTF-16 internally, which uses one or two 16-bit units per code point. That's why 👍 has a length of 2 in JavaScript: it's one code point stored as two UTF-16 units.
The family emoji is weirder still. It isn't one code point. It's a man, a woman and a girl, glued together with invisible characters called zero-width joiners. Systems that know the sequence draw one picture; older ones show three people side by side. Skin tones work similarly, with a modifier code point after the base emoji. Flags are pairs of special letter symbols. And plenty of accented letters can be typed either as a single code point or as a plain letter followed by a combining accent mark.
So there are at least three different things you could mean by "length":
- Bytes, which is what matters for storage and network limits.
- Code points, the Unicode numbers.
- Grapheme clusters, which is what a human would call a character. The family emoji is one grapheme.
Most languages give you bytes or code units by default, not graphemes. If you're showing a character counter or trimming text to fit, you usually want graphemes. In JavaScript, Intl.Segmenter can split a string into graphemes. In PHP, the intl extension has grapheme_strlen() and friends. The mb_ functions in PHP work on code points, which is better than the byte-based strlen() but still won't keep an emoji family together.
That leads to a classic bug: truncation. Chop a string at a fixed byte count and you can cut a UTF-8 character in half, leaving garbage at the end. Chop at a code point count and you can split an emoji family into a lone man and a stray joiner. Truncate by graphemes when people will see the result.
Comparison is another trap. Because "é" can be written two ways, two strings that look identical can fail an equality check. Unicode normalization fixes this by converting text to a standard form. NFC (composed) is a sensible default for storing text. If you're comparing usernames or search terms, normalize first.
Then there's the database. In MySQL and MariaDB, the old utf8 character set only stores up to three bytes per character, so emoji (which need four) either error out or get mangled. Use utf8mb4 for your tables and your connection. It's the one setting that fixes a surprising number of "why did my emoji turn into question marks" reports.
And if you ever see text like "café" where "café" should be, that's mojibake. Something wrote UTF-8 and something else read it as a different encoding, usually an old Windows or Latin-1 one. The fix is making sure every layer agrees: the file, the database connection, the HTTP Content-Type header with charset=utf-8, and the <meta charset="utf-8"> in your HTML.
One last thing for anyone handling usernames or domains. Some characters from different alphabets look identical, like the Latin "a" and the Cyrillic "а." That gets used in phishing, so if your app lets people pick names that others will trust, it's worth restricting the allowed characters or at least checking for mixed scripts.
My advice: use UTF-8 everywhere, use utf8mb4 in MySQL, normalize before comparing, and when a person's going to see the result, count graphemes. Do that and text mostly behaves. Mostly.

Comments