Last updated:
Emoji Character Counting: Why One Emoji Can Count as Multiple Characters
Emoji History and Growth by Unicode Version
The starting point for emoji spreading worldwide was 1999, when Shigetaka Kurita at NTT DoCoMo designed a set of 176 icons for the i-mode mobile platform, rendered as 12×12 pixel art. Initially, each Japanese mobile carrier (DoCoMo, au, SoftBank) implemented emoji independently, causing frequent garbled text when messages were sent between carriers. To resolve this compatibility issue, Google and Apple proposed emoji standardization to the Unicode Consortium, and 722 emoji were officially adopted in Unicode 6.0 in 2010.
Emoji kept being added in later Unicode releases, but the size of each batch varies enormously from version to version. The figures below are the ones that can be traced back to published release material.
| Unicode Version | Release Year | Emoji Added |
|---|---|---|
| 6.0 | 2010 | 722 |
| 7.0 | 2014 | about 250 |
| 8.0 | 2015 | 41 |
| 16.0 | 2024 | 8 |
One thing to keep in mind is that the "total number of emoji" moves with the definition you count by. Counting only standalone code points gives a very different answer from counting skin-tone variants and ZWJ sequences as separate entries, even at the same moment in time, which is why published totals disagree so widely. The Unicode Consortium's own tally, in the Emoji 17.0 edition, comes to 3,953 including derived forms.
The number of new additions has been declining in recent years. The Unicode Consortium now rigorously evaluates whether a proposed emoji can be represented by combining existing emoji via ZWJ sequences, and declines to allocate new code points when composition is feasible. Proposals must also carry evidence of expected usage frequency, drawn from sources such as Google Search, Google Trends, and the Google Books Ngram Viewer, together with a case that the new design is clearly distinguishable from existing emoji.
Byte Size by Encoding: Measured Data
| Emoji | Visual | Code Points | UTF-8 Bytes | UTF-16 Bytes | UTF-32 Bytes |
|---|---|---|---|---|---|
| 😀 (U+1F600) | 1 char | 1 | 4 | 4 | 4 |
| 👍🏻 (U+1F44D U+1F3FB) | 1 char | 2 | 8 | 8 | 8 |
| 👨👩👧👦 | 1 char | 7 | 25 | 22 | 28 |
| 🇺🇸 (U+1F1FA U+1F1F8) | 1 char | 2 | 8 | 8 | 8 |
| 1️⃣ (U+0031 U+FE0F U+20E3) | 1 char | 3 | 7 | 6 | 12 |
| 🏳️🌈 | 1 char | 4 | 14 | 12 | 16 |
In UTF-8, code points outside the Basic Multilingual Plane (BMP) consume 4 bytes each. UTF-16 represents them as surrogate pairs (two 16-bit units), also totaling 4 bytes. UTF-32 is fixed-width at 4 bytes per code point, making calculations simple but memory efficiency the worst of the three. The family emoji 👨👩👧👦 consuming 25 bytes in UTF-8 is a critical consideration when designing database column sizes.
How ZWJ, Variation Selectors, and Surrogate Pairs Work
Unicode uses ZWJ (Zero Width Joiner, U+200D) to combine multiple code points into a single visual emoji. The family emoji 👨👩👧👦 is composed of "man (U+1F468) + ZWJ + woman (U+1F469) + ZWJ + girl (U+1F467) + ZWJ + boy (U+1F466)" - 7 code points total.
This design was adopted because registering every combination individually would multiply out very quickly. Give each of the four members of a family emoji one of the 5 skin tone levels and you already have 5 to the power of 4, or 625, variants of that single icon, and multiplying by the different family compositions pushes the figure into the thousands. ZWJ composition allows diversity through combining basic building blocks, preventing code point exhaustion.
The dashed cells are the ZWJ code points, which never appear on screen. The four person emoji live outside the BMP, so each one takes a surrogate pair of 2 UTF-16 units and 4 UTF-8 bytes, while each ZWJ takes 1 unit and 3 bytes. The very same icon returns 1 (Swift .count), 7 (Python 3 len()), or 11 (JavaScript .length) depending only on which layer you count. Because it reaches 25 bytes in UTF-8, MySQL's utf8 charset - capped at 3 bytes per character - cannot store it at all.
Beyond ZWJ, several invisible code points control emoji display:
- Variation Selectors: U+FE0F (emoji presentation) and U+FE0E (text presentation). For example, ❤ (U+2764) displays as ❤️ (color emoji) with U+FE0F, or ❤︎ (text symbol) with U+FE0E. These invisible characters add to the byte count without being visible to users.
- Skin Tone Modifiers: U+1F3FB through U+1F3FF provide 5 levels based on the Fitzpatrick scale (a dermatological skin tone classification). Each modifier adds 1 code point (4 bytes).
- Regional Indicator Symbols: Flag emoji use 26 Regional Indicator Symbols (U+1F1E6–U+1F1FF) corresponding to A–Z, combined in pairs. 🇺🇸 is U+1F1FA (U) + U+1F1F8 (S). These map to ISO 3166-1 country codes, so undefined combinations (e.g., U+1F1FF + U+1F1FF) produce undefined rendering.
JavaScript's internal string representation uses UTF-16, so emoji outside the BMP (U+10000 and above) are represented as surrogate pairs - two 16-bit code units. This is the fundamental reason "😀".length returns 2 instead of 1. Splitting a surrogate pair (high surrogate U+D800–U+DBFF, low surrogate U+DC00–U+DFFF) produces an invalid string, making string truncation particularly dangerous.
Platform-Specific Emoji Counting and Rendering Differences
- X (Twitter): Emoji are internally counted as 2 characters each, regardless of complexity. Even the ZWJ family emoji 👨👩👧👦 counts as just 2. CJK characters, Japanese included, are also counted as 2 each, so emoji and Japanese text carry exactly the same weight there. Within the 280-character limit, heavy emoji use can cause posts to be cut short if you don't account for this.
- Instagram: In the 2,200-character caption limit, emoji count as 1 character each. The ceiling leaves plenty of headroom, but the counting unit is not the one X uses, so a caption written for Instagram and then reused verbatim can overflow on the X side alone.
- LINE: Text messages count emoji as 1 character. However, LINE's proprietary sticker emoji and Unicode emoji are handled differently internally, which is important for developers working with the LINE Messaging API.
- Slack: Message body counts emoji as 1 character, but custom emoji use shortcodes (e.g.,
:thumbsup:), and the entire shortcode string including colons counts toward the character limit. - SMS: Including even one emoji switches encoding from GSM-7 to UCS-2, reducing the per-message limit from 160 to 70 characters. Barely more than 40 percent of the original text now fits into a single message, so the same body copy has to be split across more messages, and for marketing SMS the total sending cost climbs with it.
The same emoji can also look dramatically different across Apple, Google, Samsung, and Microsoft platforms. For example, 🔫 (pistol) was changed to a toy water gun design by Apple, and other vendors followed - but at different times. When emoji appearance matters in marketing or UI design, preview your content across major platforms before publishing.
Character Count Differences Across Programming Languages
The same emoji produces different length values depending on the programming language, because each language uses a different internal string representation.
| Language / Method | "😀" | "👍🏻" | "👨👩👧👦" | Count Unit |
|---|---|---|---|---|
JavaScript .length | 2 | 4 | 11 | UTF-16 code units |
JavaScript [...str].length | 1 | 2 | 7 | Unicode code points |
Python 3 len() | 1 | 2 | 7 | Unicode code points |
Rust .len() | 4 | 8 | 25 | UTF-8 bytes |
Rust .chars().count() | 1 | 2 | 7 | Unicode code points |
Swift .count | 1 | 1 | 1 | Grapheme clusters |
Go len() | 4 | 8 | 25 | UTF-8 bytes |
Java .length() | 2 | 4 | 11 | UTF-16 code units |
Only Swift counts by grapheme clusters, returning 1 for any emoji regardless of internal complexity. JavaScript and Java use UTF-16 internally, so emoji outside the BMP are counted as 2 (surrogate pairs). Rust and Go return byte counts, making them unsuitable for character counting without additional processing. Developers must understand exactly what their language's length returns.
Common Mistakes and How to Avoid Them
- Using
String.lengthfor character limits in JavaScript:"👨👩👧👦".lengthreturns 11, but the visual count is 1. Form validation usingString.lengthwill unfairly restrict emoji-heavy input. UseIntl.Segmenterfor accurate grapheme cluster counting. - Truncating strings mid-ZWJ sequence: Cutting the family emoji 👨👩👧👦 at an arbitrary byte or code-unit position breaks the ZWJ sequence, causing individual person emoji to appear separately - or worse, producing invalid characters. Always truncate at grapheme cluster boundaries.
- Using MySQL
utf8instead ofutf8mb4: MySQL'sutf8charset supports only up to 3 bytes per character, which cannot store emoji (4-byte code points outside the BMP). You must useutf8mb4. Note also that the 255 inVARCHAR(255)is a character count, not a byte count: a single family emoji spends 7 of those characters because it is 7 code points, so 36 of them are enough to fill the column even though only 36 icons are visible. Row size and index length are capped separately, and those limits are measured in bytes. PostgreSQL supports the full UTF-8 range by default, so this issue doesn't arise. - Matching emoji with basic regex: JavaScript's
/./matches only one half of a surrogate pair. Adding the Unicode flag, as in/./u, raises the unit to one full code point. Neitherunorvmakes.reach beyond that single code point, so to treat an entire ZWJ sequence as one unit you combine thevflag (Unicode Sets, ES2024) with the string property\p{RGI_Emoji}. - Overusing emoji in email subject lines: Some email clients fail to render emoji correctly, displaying garbled text or blank spaces. For business emails, it's safer to minimize emoji usage given the unpredictable rendering across recipients' environments.
Developer Guide: Counting Emoji Accurately
- Grapheme cluster segmentation with
Intl.Segmenter: Standardized in the internationalization API (ECMA-402),Intl.Segmentersplits strings by grapheme clusters - the units users perceive as single characters.[...new Intl.Segmenter().segment(str)].lengthgives the accurate visual character count for any emoji. As of August 2026 it is available in Chrome 87+, Safari 14.1+, Node.js 16+, and Firefox 125+. - Emoji detection and removal with regex: The Unicode property escape
/\p{Emoji_Presentation}/udetects emoji in strings. Note that\p{Emoji}also matches digits (0-9) and #, so use\p{Emoji_Presentation}or\p{Extended_Pictographic}when targeting only pictorial emoji. - Comprehensive emoji test set: When testing, cover these 5 categories at minimum: (1) basic emoji (😀), (2) skin tone modified (👍🏻), (3) ZWJ sequences (👨👩👧👦), (4) flags (🇺🇸), and (5) keycap sequences (1️⃣).
- Database design best practices: Choose the N in
VARCHAR(N)knowing that it counts characters and that one grapheme cluster can swallow 7 of them. When you then estimate row size and index length, convert at up to 4 bytes per character. In MySQL, useutf8mb4, and for chat applications with heavy emoji use, consider using theTEXTtype instead.
Conclusion
Emoji counting is more complex than it appears. Different platforms and programming languages count emoji differently. Use Character Counter to get accurate character counts that account for emoji complexity.