String Length, Four Ways

After reading this you will know why one string can have four different "lengths" at once, and which count your database, your programming language and your users each care about.

Four counts for one string

Type the family emoji 👨‍👩‍👧 into a text box and ask for its length. JavaScript says 8. Python 3 says 5. Your eyes say 1. A UTF-8 byte counter says 18. Nobody is lying. Each number answers a different question.

Text is stored in layers. Users see graphemes, the visible units you can select with one caret move. Unicode describes text as a sequence of code points, integers from 0 to 1114111. Machines store those code points as bytes, using an encoding such as UTF-8 or UTF-16. The String Length Inspector shows all four at once so you can see where they diverge.

Grapheme
A user-perceived character, one visible glyph, defined by Unicode's grapheme cluster rules (UAX #29).
Code point
One Unicode scalar value in the range U+0000 to U+10FFFF.
UTF-8 byte
One byte of the UTF-8 encoding. Each code point takes 1 to 4 bytes.
UTF-16 code unit
One 16-bit unit. Code points above U+FFFF take 2 units (a surrogate pair).

When these counts matter

You reach for byte counts when something has a byte budget. A database column declared VARCHAR(20) may store 20 bytes or 20 characters depending on the engine and charset. A DNS label is capped at 63 bytes. An HTTP header value, a QR code, a fixed-width protocol field: all measured in bytes.

You care about UTF-16 units when you validate input in JavaScript, Java, or C#, because their string .length counts UTF-16 units. You care about graphemes when a human sees the result: a 280-character tweet limit, a username display, a truncation preview.

Truncating a string by cutting at byte offset 10 can split a multi-byte code point in half and produce invalid UTF-8. Truncating by UTF-16 units can split a surrogate pair. Truncating by code points can break a grapheme cluster, chopping an emoji's skin-tone modifier off its base. Only grapheme-aware truncation is safe for display.

How UTF-8 spends bytes

UTF-8 (defined in RFC 3629) is a variable-width encoding. The rule is simple once you see it as ranges:

\text{bytes}(c) = \begin{cases} 1 & c \le \text{U+007F} \\ 2 & c \le \text{U+07FF} \\ 3 & c \le \text{U+FFFF} \\ 4 & c \le \text{U+10FFFF} \end{cases}

Here c is the code point value. ASCII (U+0000 to U+007F) fits in one byte, which is why English text costs one byte per character. Latin letters with accents and most European scripts land in the 2-byte range. The bulk of the Basic Multilingual Plane, including é's neighbours, CJK ideographs and most symbols, takes 3 bytes. Emoji and rare scripts above U+FFFF take 4.

UTF-16 splits differently. Everything up to U+FFFF is one 16-bit unit. Everything from U+10000 up is a surrogate pair, so two units. There is no 3-unit case.

UTF-8 grows in four steps; UTF-16 only has one jump, at U+10000.

Worked example: the family emoji

Breaking down 👨‍👩‍👧

The sequence looks like one face-and-family glyph. Under the hood it is three human emoji joined by an invisible connector.

Code point breakdown of 👨‍👩‍👧
Code pointNameUTF-8 bytesUTF-16 units
U+1F468Man42
U+200DZero Width Joiner31
U+1F469Woman42
U+200DZero Width Joiner31
U+1F467Girl42
  1. Count code points: 5 entries in the table, so 5 code points.
  2. Sum UTF-8 bytes: 4 + 3 + 4 + 3 + 4 = 18, so 18 bytes.
  3. Sum UTF-16 units: 2 + 1 + 2 + 1 + 2 = 8, so JavaScript's .length reports 8.
  4. Apply grapheme clustering: the two zero-width joiners (U+200D) bind the three faces into one cluster, so 1 grapheme.

That is the full spread: 1 grapheme, 5 code points, 8 UTF-16 units, 18 UTF-8 bytes. Paste the emoji into the tool and you will see these exact numbers, plus the per-character table above.

The Zero Width Joiner is the glue. Remove both joiners and you get three separate emoji: 3 graphemes, 3 code points, 12 UTF-8 bytes. The visible result changes from one family to three lone faces.

Explore the counts yourself

Consider the string "café🎉". It has 5 graphemes, 5 code points, 6 UTF-16 units (the party popper is a surrogate pair, so 2), and 8 UTF-8 bytes (c, a, f are 1 each, é is 2, 🎉 is 4). Adding a second 🎉 raises bytes to 12 and UTF-16 units to 8, but graphemes only to 6.

Reading the per-character breakdown

The tool lists each code point with its hex value, name and byte cost. Three patterns are worth recognising.

First, combining marks. The letter é can arrive two ways. As one precomposed code point U+00E9 it is 2 UTF-8 bytes and 1 grapheme. As e (U+0065) plus a combining acute accent (U+0301) it is 3 UTF-8 bytes, 2 code points, still 1 grapheme. Same glyph on screen, different counts. This is why grapheme count and code point count can disagree even without emoji.

Second, surrogate pairs. Any code point at or above U+10000 shows as 2 UTF-16 units. That is the entire reason JavaScript reports 8 for the family emoji instead of 5.

Third, invisible code points. Zero Width Joiners, variation selectors (U+FE0F forces emoji presentation) and directional marks all cost bytes but draw nothing on their own. If a byte count looks larger than expected, scan the table for these.

Common mistakes

Sizing a column to VARCHAR(255) and assuming 255 user characters. In MySQL with the utf8mb4 charset a character can take 4 bytes, and older definitions counted bytes, so a string of 255 emoji could need 1020 bytes and get rejected or silently truncated.

Validating a password or bio length with JavaScript's str.length and calling it "characters". A user who types 140 emoji sees a count of 280 and a rejected form. Count graphemes for a human-facing limit, or code points if you must count in code.

Reversing a string by reversing its code units. Reverse 👍 byte-pair by pair and you swap the surrogate halves, producing an invalid pair that renders as ?. Reverse by grapheme instead.

When you need bytes for a storage or transfer budget, convert the byte total with the Byte size converter to check it against a limit expressed in KiB or MB.

Related tools

Once you know a string's byte layout, the next questions are usually about transforming or protecting it. To turn text into a transport-safe form and back, use the Base64 / URL / HTML encoder-decoder; note that Base64 inflates byte length by about 33 percent. To fingerprint a string, the Hash generator gives you MD5 through SHA-512, and the HMAC calculator signs a message with a secret key. If your string is a token, the JWT decoder splits and reads its parts locally.

Frequently asked questions

Why does JavaScript say my emoji is length 2?

JavaScript strings are UTF-16, and any code point above U+FFFF is stored as a surrogate pair of two 16-bit units. The .length property counts units, so one astral emoji reads as 2. Use [...str].length to count code points, or an Intl.Segmenter to count graphemes.

How do I count what the user sees?

Count graphemes. That is the grapheme cluster count from Unicode UAX #29, which merges combining marks, surrogate pairs and joiner sequences into single visible units. The tool reports this as the grapheme total.

How many bytes will my text take in a database?

For a utf8mb4 or UTF-8 column, it is the UTF-8 byte count the tool shows. Multiply by the row count for a rough table estimate, and remember that a VARCHAR(n) defined in bytes will reject strings whose byte count exceeds n.

Are code points and characters the same thing?

Not always. A "character" in the everyday sense is a grapheme. One grapheme can be several code points, as with e plus a combining accent (2 code points, 1 grapheme) or the family emoji (5 code points, 1 grapheme).

Does this tool send my text anywhere?

No. All counting runs in your browser, so nothing you paste leaves your device.