---
url: https://findutils.com/blog/why-an-emoji-counts-as-2-characters-string-length-explained
title: "Why an Emoji Counts as 2 Characters: String Length in JavaScript, Python, PHP and SQL"
description: "One thumbs-up with a skin tone is 1 character on screen, 2 in Python, 4 in JavaScript and 8 bytes in UTF-8. Why string length depends on what you count, and how to make a form's limit agree everywhere."
category: text_tools
content_type: blog
cluster: text-length
locale: en
read_time: 8
status: published
author: "olgunozoktas"
published_at: 2026-10-24T08:08:00Z
excerpt: "Ask four programs how long 👍🏽 is and you get four answers: 1, 2, 4 and 8. None of them is wrong. They count different things: what you see, Unicode code points, UTF-16 code units and UTF-8 bytes. Here is which language, form field, database and platform counts which, with measured numbers, and how to keep a character limit consistent from the browser to the database."
tag_ids: ["unicode", "string-length", "emoji", "text-tools", "forms"]
tags: ["Unicode", "String Length", "Emoji", "Text Tools", "Forms"]
primary_keyword: "why does an emoji count as 2 characters"
secondary_keywords: ["javascript string length emoji", "utf-16 code units vs code points", "grapheme cluster length", "php strlen vs mb_strlen", "maxlength emoji", "character limit unicode"]
tool_tag: "character-counter"
related_tool: "character-counter"
related_tools: ["character-counter", "text-truncate", "url-encoder-decoder", "emoji-finder", "word-counter", "html-entity-finder"]
updated_at: "2026-10-24T08:08:00Z"
og_image: "/images/content/blog/why-an-emoji-counts-as-2-characters-string-length-explained-cover-20261024.webp"
image_alt: "A glowing yellow smiley face with a brown skin-tone square beside it, connected by lines to four rows of blocks of length 1, 2, 4 and 8 above a ruler, on a dark background."
---

An emoji often counts as 2 characters because JavaScript, Java, C# and the HTML `maxlength` attribute measure text in **UTF-16 code units**, and most emoji sit outside the range one code unit can hold, so each takes two. Other systems count differently: Python counts **code points**, PHP's `strlen` counts **bytes**, and a person counts what they see, which Unicode calls a **grapheme cluster**. The thumbs-up with a medium skin tone, 👍🏽, is 1 grapheme, 2 code points, 4 UTF-16 code units and 8 UTF-8 bytes.

You can see the UTF-16 count for yourself: paste 👍🏽 into the FindUtils [Character Counter](/text/character-counter/), which counts the way JavaScript does, and it shows 4.

## Four Ways to Measure the Same Text

| Unit | What it counts | 👍🏽 | Who counts this way |
|------|----------------|-----|---------------------|
| Grapheme cluster | What a reader sees as one character | 1 | `Intl.Segmenter`, PHP `grapheme_strlen`, Swift `String.count` |
| Code point | Unicode's numbered characters | 2 | Python `len`, PHP `mb_strlen`, MySQL `CHAR_LENGTH`, PostgreSQL `length` |
| UTF-16 code unit | 16-bit pieces of the UTF-16 encoding | 4 | JavaScript `.length`, HTML `maxlength`, Java, C#, SQL Server `nvarchar(n)` |
| UTF-8 byte | 8-bit pieces of the UTF-8 encoding | 8 | PHP `strlen`, MySQL `LENGTH`, file and request sizes |

**Code points.** Unicode gives every character a number. 👍 is U+1F44D, and the medium skin tone is a separate code point, U+1F3FD, a modifier that changes the emoji before it. So 👍🏽 is two code points.

**UTF-16 code units.** UTF-16 stores code points up to U+FFFF in one 16-bit unit. Anything above that, which includes most emoji, is stored as a *surrogate pair* of two units. Each of the two code points in 👍🏽 needs a pair, so the total is 4. The ECMAScript specification defines a string as a sequence of 16-bit values, and `length` is how many there are.

**UTF-8 bytes.** UTF-8 uses 1 byte for ASCII, 2 for most accented Latin letters, 3 for most Chinese, Japanese and Korean characters, and 4 for emoji. Two emoji code points make 8 bytes. To see the bytes, put 👍 into the [URL Encoder](/network/url-encoder-decoder/): it becomes `%F0%9F%91%8D`, one `%` group per byte.

**Grapheme clusters.** Unicode Standard Annex #29 defines the "user-perceived character": a base character plus anything attached to it, such as a skin-tone modifier, a combining accent or emoji joined by a zero-width joiner. That is the count most people mean when they say "characters".

## Measured: Eight Strings, Five Counts

We measured these strings on October 24, 2026 with Node.js (`.length`, `[...s].length`, `Intl.Segmenter` and `Buffer.byteLength`) and with X's open-source counting library, twitter-text 3.

| Text | Graphemes | Code points | UTF-16 units | UTF-8 bytes | X count |
|------|-----------|-------------|--------------|-------------|---------|
| `A` | 1 | 1 | 1 | 1 | 1 |
| `é` (one code point, U+00E9) | 1 | 1 | 1 | 2 | 1 |
| `é` (e + combining accent U+0301) | 1 | 2 | 2 | 3 | 1 |
| `中` | 1 | 1 | 1 | 3 | 2 |
| 👍 | 1 | 1 | 2 | 4 | 2 |
| 👍🏽 | 1 | 2 | 4 | 8 | 2 |
| 👨‍👩‍👧 (family: 3 people, 2 joiners) | 1 | 5 | 8 | 18 | 2 |
| 🇹🇷 (flag: 2 regional indicator letters) | 1 | 2 | 4 | 8 | 2 |

Two rows deserve a second look. The two forms of `é` look identical, but one is a single code point and the other is `e` followed by a combining accent, so the code point, UTF-16 and byte counts differ. Text copied from some apps and documents uses the second form, so two strings that look the same can have different lengths. And the family emoji is one picture made of five code points, which is why it can count as 8 in a JavaScript form.

## Who Counts What

- **JavaScript:** `"👍🏽".length` is 4. `[..."👍🏽"].length` iterates by code point and gives 2. `Intl.Segmenter` with `granularity: "grapheme"` gives 1, and works in all current major browsers.
- **HTML forms:** the HTML Standard measures `maxlength` and `minlength` in code units, the same as `.length`, with each line break in a `<textarea>` counted as one character.
- **Python:** `len("👍🏽")` is 2. Python 3 strings are sequences of code points.
- **PHP:** `strlen` returns bytes (8), `mb_strlen` with UTF-8 returns code points (2), and `grapheme_strlen` from the intl extension returns graphemes (1).
- **MySQL:** `VARCHAR(n)` limits characters, which here means code points, and `CHAR_LENGTH` counts them; `LENGTH` counts bytes. Emoji need the `utf8mb4` character set: the older `utf8mb3` stores at most 3 bytes per character and cannot hold them.
- **PostgreSQL:** `varchar(n)` stores up to n characters (code points), not bytes.
- **SQL Server:** in `nvarchar(n)`, Microsoft's documentation says n is a number of byte-pairs, not characters, so an emoji takes two of the n.
- **X (Twitter):** X weights characters. Most Latin, Cyrillic and Greek letters count 1, Chinese, Japanese and Korean characters count 2, every emoji counts 2 however many code points it has, and every link counts 23, against a limit of 280.
- **SMS:** a single message holds 160 characters of the GSM-7 alphabet. One character outside it, such as an emoji or a curly quote, switches the whole message to an encoding that holds 70 per message, and long messages are split into parts of 153 or 67.

## Where the Mismatch Bites

**A form that the browser accepts and the server rejects.** A `<textarea maxlength="280">` counts code units, and a line break counts as one. But when a classic HTML form is submitted, the HTML Standard converts every line break to CR LF, two characters. A 280-character message with five line breaks arrives at the server as 285 characters, and a server-side "maximum 280" rule refuses text the browser allowed. Requests sent by JavaScript as JSON keep a single line feed, so the same form can behave differently depending on how it is submitted.

**A form that the server accepts and the browser blocks.** The opposite also happens. Laravel's `max:280` rule measures a string with `mb_strlen`, which counts code points, while the browser's `maxlength` counts code units. Emoji-heavy text hits the browser limit first: 70 thumbs-up with a skin tone is 140 code points but 280 code units, so the browser stops at 70 while the server would accept up to 140.

**A database that cuts text off or rejects it.** A column sized in bytes, or `nvarchar` sized in byte-pairs, fills up sooner than a character limit suggests. In MySQL with `utf8mb3`, an emoji is not stored at all: depending on the SQL mode, the insert fails or the text is cut at the emoji.

**A truncation that breaks a character.** Cutting a string at a code-unit position, as `text.slice(0, 160)` does, can split a surrogate pair and leave half an emoji, which displays as a replacement character, or split a flag into a single letter. The [Text Truncate](/text/text-truncate/) tool counts in code units like the browser, but moves each cut back to the start of the character, so an emoji, a flag or an accented letter is never split.

## How to Keep a Limit Consistent

1. **Decide what the limit means.** "What the user sees" is graphemes. "What fits in the column" is the unit your database counts. They are different numbers, and the product rule should say which one it means.
2. **Count the same way on both sides.** If the server counts code points with `mb_strlen`, count code points in the browser too (`[...value].length`) and show that number, rather than relying on `maxlength` alone.
3. **Normalize line breaks before counting.** Convert CR LF to LF on the server before you check a length, so a classic form post and a JSON request are measured the same way.
4. **Normalize Unicode when it matters.** Converting text to NFC (`normalize("NFC")` in JavaScript, `Normalizer::normalize` in PHP) turns `e` + combining accent into the single `é`, so the same visible text always has the same length. X's library does this before it counts, which is why both forms of `é` count 1 there.
5. **Size columns for the worst case.** For a limit of n code points in UTF-8, plan for up to 4n bytes; in `nvarchar`, up to 2n byte-pairs.
6. **Cut on character boundaries.** Use `Intl.Segmenter` in JavaScript or `grapheme_substr` in PHP, or check the cut with Text Truncate before you hard-code an expected value in a test.

## Check a Length With FindUtils

- **[Character Counter](/text/character-counter/):** counts characters the way JavaScript and `maxlength` do, plus words, sentences and lines. Its bars for X, Instagram, LinkedIn and others compare that count with each platform's number, so for text with emoji, links or Chinese, Japanese or Korean characters, treat them as a guide and check where you post.
- **[Text Truncate](/text/text-truncate/):** cuts text to a limit without splitting an emoji, a flag or an accented letter, and shows the character count before and after.
- **[URL Encoder](/network/url-encoder-decoder/):** shows the UTF-8 bytes of any character as `%XX` groups, which is the quickest way to see a byte count.
- **[Emoji Finder](/generate/emoji-finder/):** search and copy an emoji to test how your own form or database counts it.

Need help with a project? The team behind FindUtils takes on [freelance work](https://www.upwork.com/freelancers/~010a5875eab37b4bd7), such as [a Laravel Livewire app or admin panel with dynamic pages and forms](https://www.upwork.com/services/product/development-it-a-laravel-livewire-app-or-admin-panel-with-dynamic-pages-and-forms-2112447485311211142).

## FAQ

### Why does JavaScript say an emoji has length 2?

JavaScript strings are sequences of UTF-16 code units, and `.length` counts those units. Emoji are above U+FFFF, so each needs a surrogate pair of two units. Emoji with a skin tone or several joined people need more: 👍🏽 is 4 and 👨‍👩‍👧 is 8.

### How do I count characters the way a person sees them?

Count grapheme clusters. In JavaScript, use `[...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(text)].length`. In PHP, use `grapheme_strlen`. Both count 👍🏽 and 🇹🇷 as 1.

### Does HTML maxlength count emoji as 1 or 2?

As 2 for most single emoji. The HTML Standard measures `maxlength` in UTF-16 code units, the same as JavaScript's `.length`. Line breaks in a `<textarea>` count as one each.

### Why does PHP strlen give a bigger number than mb_strlen?

`strlen` counts bytes and `mb_strlen` counts characters (code points). In UTF-8, any character outside ASCII takes 2 to 4 bytes, so the byte count is higher whenever the text has an accent, a non-Latin letter or an emoji.

### How many characters does an emoji use on X?

Two, regardless of how many code points it is made of. X's documentation says all emoji count as 2, including ones with a skin tone and ones joined with zero-width joiners.

### Can I store emoji in a MySQL VARCHAR column?

Yes, if the column uses the `utf8mb4` character set. The older `utf8mb3` (long called just `utf8`) stores at most 3 bytes per character and cannot store emoji, which need 4.

## Related Reading

- [Text Truncate guide](/guides/text-truncate-guide/)
- [Word Counter guide](/guides/word-counter-guide/)
- [URL encoding explained: percent-encoding and query strings](/blog/url-encoding-explained-percent-encoding-and-query-strings/)
- [Title tag length: why Google truncates by pixels, not characters](/blog/title-tag-length-why-google-truncates-by-pixels-not-characters/)

## Sources

- [Unicode Standard Annex #29: Unicode Text Segmentation](https://www.unicode.org/reports/tr29/)
- [ECMAScript Language Specification: The String Type](https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type)
- [MDN: Intl.Segmenter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter)
- [HTML Standard: the maxlength attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-maxlength)
- [HTML Standard: converting form entries to name-value pairs](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs)
- [PHP manual: strlen](https://www.php.net/manual/en/function.strlen.php), [mb_strlen](https://www.php.net/manual/en/function.mb-strlen.php) and [grapheme_strlen](https://www.php.net/manual/en/function.grapheme-strlen.php)
- [MySQL 8.4 Reference Manual: The utf8mb4 Character Set](https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8mb4.html)
- [PostgreSQL documentation: Character Types](https://www.postgresql.org/docs/current/datatype-character.html)
- [Microsoft Learn: nchar and nvarchar (Transact-SQL)](https://learn.microsoft.com/en-us/sql/t-sql/data-types/nchar-and-nvarchar-transact-sql)
- [X Developer Platform: Counting characters](https://docs.x.com/fundamentals/counting-characters)
- [Twilio: How long can a message be?](https://www.twilio.com/docs/glossary/what-sms-character-limit)
