How to Count Unicode Characters Correctly
To count Unicode text correctly, first decide whether you need bytes, encoding code units, Unicode code points or user-perceived grapheme clusters.
On this page
Unicode character counting at a glance
| Unit | What it counts | Example for π | Typical use |
|---|---|---|---|
| Bytes | Encoded storage units | 4 in UTF-8 | Storage, payload and file-size limits |
| UTF-16 code units | 16-bit encoding units | 2 | JavaScript indexes and string length |
| Code points | Unicode scalar values | 1 | Unicode inspection and processing |
| Grapheme clusters | User-perceived characters | 1 | UI limits, cursor movement and truncation |
βCharacterβ can mean several different things. Always name the unit you are counting.
Why Unicode character counting is difficult
ASCII creates a convenient illusion: one visible character, one code point and one byte. Unicode breaks that shortcut because it supports far more writing systems, symbols and text behavior. UTF-8 is variable-length, UTF-16 may use surrogate pairs, and one visible character can contain several code points. Combining marks modify preceding letters, emoji may include skin-tone modifiers, variation selectors and zero-width joiners, and database or API functions may count different units. The strings A, Γ©, e + βΜ, π, ππ½ and π¨βπ©βπ§βπ¦ can all look like short text while producing different byte, code-unit, code-point and grapheme counts.
Bytes vs code units vs code points vs grapheme clusters
Bytes
A byte is 8 bits. Byte count depends on encoding: UTF-8 uses one to four bytes per code point, while UTF-16 stores text differently. Use byte counts for storage, network, file-size, message-queue and payload limits.
Code units
Code units belong to an encoding. UTF-8 code units are 8-bit bytes; UTF-16 code units are 16 bits; UTF-32 code units are 32 bits. JavaScript exposes string length as UTF-16 code units.
Code points
A code point is one Unicode value. Code-point count is independent of UTF-8 or UTF-16 and is useful for Unicode inspection, validation and security rules. A grapheme cluster can still contain several code points. Start with What Is Unicode? and What Is a Unicode Code Point? if those terms are new.
Grapheme clusters
A grapheme cluster is the unit that most closely matches a user-perceived character. Unicode text-segmentation rules define boundaries. It is usually the right unit for display names, text previews, cursor movement and backspace behavior. See Code Points vs Code Units and What Is a Grapheme Cluster?.
Character-count examples
| Text | Grapheme clusters | Code points | UTF-16 code units | UTF-8 bytes |
|---|---|---|---|---|
A | 1 | 1 | 1 | 1 |
Γ© | 1 | 1 | 1 | 2 |
eΜ | 1 | 2 | 2 | 3 |
π | 1 | 1 | 2 | 4 |
ππ½ | 1 | 2 | 4 | 8 |
πΊπΈ | 1 | 2 | 4 | 8 |
π¨βπ©βπ§βπ¦ | 1 where supported | Multiple | Multiple | Multiple |
The visible result can be one character even when the string contains many code points and code units. Complex emoji totals should be verified with the same segmentation rules and Unicode version used in production.
Interactive Unicode character counter
This embedded counter reuses UnicodeNowβs grapheme segmentation and byte-inspection utilities. It counts locally and shows where grapheme clusters begin and end.
Unicode Character Count Analyzer
Compare grapheme clusters, code points, UTF-16 units and UTF-8 bytes for the same text.
How to count UTF-8 bytes
UTF-8 uses one to four bytes per code point. ASCII characters use one byte, many accented characters use two, many CJK characters use three, and supplementary emoji commonly use four. Multi-code-point graphemes use the sum of all encoded bytes.
A β 1 byte
Γ© β 2 bytes
ε β 3 bytes
π β 4 bytesUTF-8 byte count matters for API payload limits, database byte-limited fields, files, queues, network protocols and index-size constraints. Use Byte Length Calculator or UTF-8 Encoder and Decoder.
How to count UTF-16 code units
UTF-16 uses one code unit for most Basic Multilingual Plane code points. Supplementary code points use a surrogate pair, so one emoji can have length 2 in JavaScript. ZWJ emoji sequences can have much larger code-unit lengths.
console.log("A".length); // 1
console.log("Γ©".length); // 1
console.log("π".length); // 2UTF-16 code-unit indexes are appropriate when working with JavaScript native string offsets or browser APIs that explicitly document UTF-16 offsets. UTF-16 code-unit count is not a visible-character count.
How to count Unicode code points
Code-point iteration combines valid surrogate pairs. JavaScript for...of and spread syntax iterate code points, Python string iteration usually yields code points, and PHP requires multibyte-aware functions. Combining marks, ZWJ and variation selectors still count as separate code points.
const text = "Aπ";
console.log([...text].length); // 2Code-point count is useful for property inspection, scalar-value limits, encoding algorithms and security rules based on exact Unicode values. It is not necessarily right for visible character limits, cursor movement or display truncation.
How to count grapheme clusters
Grapheme clusters correspond most closely to user-perceived characters. A base character plus combining marks is one cluster, emoji modifiers stay with the base emoji, regional indicator pairs can form flags, and ZWJ sequences can form composite emoji. Unicode segmentation rules evolve, so the runtime and library version matter.
const segmenter = new Intl.Segmenter("en", {
granularity: "grapheme",
});
const text = "π¨βπ©βπ§βπ¦";
const graphemes = [...segmenter.segment(text)];
console.log(graphemes.length); // Usually 1Grapheme clusters are appropriate for user-facing input limits, display names, chat-message truncation, cursor movement, backspace behavior, previews and UI labels. Grapheme count is usually the right answer when a product requirement says βvisible characters.β
Why JavaScript String.length is misleading
JavaScript strings are sequences of UTF-16 code units, and length returns code units. A supplementary character uses two units, and a multi-code-point grapheme uses even more. Spread syntax improves one layer by counting code points, but it is not grapheme-aware.
const emoji = "π";
console.log(emoji.length); // 2 code units
console.log([...emoji].length); // 1 code point
const family = "π¨βπ©βπ§βπ¦";
console.log(family.length);
console.log([...family].length);Neither result necessarily represents visible-character count for the family emoji. Use Intl.Segmenter or a maintained fallback for grapheme-aware UI limits.
Counting Unicode in JavaScript
function countUtf16CodeUnits(text) {
return text.length;
}
function countCodePoints(text) {
return [...text].length;
}
function countUtf8Bytes(text) {
return new TextEncoder().encode(text).length;
}
function countGraphemeClusters(text, locale = "en") {
const segmenter = new Intl.Segmenter(locale, {
granularity: "grapheme",
});
return [...segmenter.segment(text)].length;
}Check Intl.Segmenter support and use the existing fallback for older environments. Do not use split(""), because it splits UTF-16 code units. Do not treat spread syntax as grapheme-aware. Keep locale handling configurable when the product has locale-specific behavior.
Counting Unicode in Python
Python len() counts code points in normal Unicode strings. It does not count UTF-8 bytes and it does not count grapheme clusters. Encoding returns bytes, and a Unicode-aware segmentation library may be needed for grapheme counting.
def count_code_points(text: str) -> int:
return len(text)
def count_utf8_bytes(text: str) -> int:
return len(text.encode("utf-8"))
import regex
def count_grapheme_clusters(text: str) -> int:
return len(regex.findall(r"\X", text))Pin and test any segmentation dependency, confirm the Unicode version, and do not use len() for user-visible character limits. The \X syntax is library-specific, not built into Pythonβs standard re module.
Counting Unicode in PHP
PHP strings are byte sequences. strlen() counts bytes, mb_strlen() counts encoding-aware characters or code points, and grapheme_strlen() counts grapheme clusters when Intl is available.
$text = "π";
echo strlen($text);
echo mb_strlen($text, "UTF-8");
echo grapheme_strlen($text);function countUtf8Bytes(string $text): int
{
return strlen($text);
}
function countCodePoints(string $text): int
{
return mb_strlen($text, "UTF-8");
}
function countGraphemeClusters(string $text): int
{
$length = grapheme_strlen($text);
if ($length === false) {
throw new RuntimeException("Unable to count grapheme clusters.");
}
return $length;
}Use mbstring, intl, valid UTF-8 input and runtime tests. PHP strings are not internally UTF-16.
Combining characters and length
Precomposed Γ© is U+00E9: one grapheme cluster, one code point, one UTF-16 code unit and two UTF-8 bytes. Decomposed e + βΜ is U+0065 U+0301: one grapheme cluster, two code points, two UTF-16 code units and three UTF-8 bytes. The forms may render identically, but code-point and byte counts differ. Normalization can change counts, while grapheme count may remain the same. Compare forms in NFC vs NFD.
Emoji and length
Emoji length depends on the sequence. A single supplementary emoji such as π is one code point, two UTF-16 units and four UTF-8 bytes. ππ½ combines a thumbs-up code point with a skin-tone modifier. πΊπΈ is two regional indicators. π¨βπ©βπ§βπ¦ is a ZWJ sequence. Variation selectors may request emoji presentation. Rendering support can vary, but byte and code-point counts still reflect the underlying sequence.
Invisible characters and character count
Invisible characters still count. Examples include U+200B ZERO WIDTH SPACE, U+200D ZERO WIDTH JOINER, U+FE0F VARIATION SELECTOR-16, U+00AD SOFT HYPHEN and U+00A0 NO-BREAK SPACE. username and userβname may look identical, but the second contains an extra code point and extra UTF-8 bytes. Normalization may not remove it. Use Invisible Character Detector and Unicode Character Inspector.
Character limits in forms
Form validation must define the unit. A display name usually wants grapheme clusters. A username may need code-point restrictions, script policy, normalization, invisible-character restrictions, byte limits and grapheme limits. A biography or chat message may need a grapheme limit for user experience and a byte limit for storage or transport. Passwords should not be trimmed, normalized or altered unless the authentication specification explicitly requires it.
| Field | Recommended primary unit |
|---|---|
| Display name | Grapheme clusters |
| Chat message | Grapheme clusters plus byte limit |
| API token | Bytes or ASCII characters |
| Username | Policy-defined code points plus grapheme and byte checks |
| Database payload | Bytes |
| Password | Protocol-defined exact input |
Character limits in databases
Database column limits may count characters or bytes, and behavior varies by database and column type. Index limits may be byte-based. Collations do not define visible-character count, normalization can change length, and emoji may require more bytes. Application and database limits must agree. When a field has both human and storage requirements, validate grapheme_count, code_point_count and utf8_byte_count explicitly.
Character limits in APIs
API specifications may limit bytes, code points or βcharacters.β That word must be clarified. JSON serialization adds syntax bytes around strings, escapes can increase serialized payload size, and transport limits usually apply to bytes. User-interface limits may apply to graphemes. A common design is: user limit of 100 grapheme clusters, API limit of 1,000 UTF-8 bytes. Enforce both independently and document both.
Safe Unicode truncation
Byte truncation must preserve valid UTF-8 boundaries. UTF-16 code-unit truncation can split surrogate pairs and should be avoided for user-visible text. Code-point truncation preserves scalar values but may split grapheme clusters. Grapheme truncation is best for visible text, but it does not enforce byte limits alone.
| Goal | Truncate by |
|---|---|
| File or payload size | Bytes, preserving encoding boundaries |
| Unicode value limit | Code points |
| User-visible preview | Grapheme clusters |
| JavaScript API offset | UTF-16 code units when required by API |
Counting words and lines is a separate problem
Word boundaries are not simple spaces in every language. Grapheme segmentation is not word segmentation. Line count depends on whether your input uses \n, \r\n, \r or Unicode line separators. Intl.Segmenter supports word segmentation in suitable environments, but product requirements still need to define word-count semantics. Do not confuse Unicode character counting with word counting.
How normalization affects length
NFC and NFD can produce different code-point counts and UTF-8 byte counts. Γ© in NFC is one code point and two UTF-8 bytes. e + βΜ in NFD is two code points and three UTF-8 bytes. Grapheme count may stay the same. NFKC and NFKD can alter compatibility characters. Normalize only according to a documented policy, not solely to reduce length. Use Unicode Normalizer and Unicode Normalization Checker.
Which counting method should you use?
| Requirement | Correct unit |
|---|---|
| Visible UI character limit | Grapheme clusters |
| Unicode property validation | Code points |
| JavaScript native string indexes | UTF-16 code units |
| UTF-8 storage limit | Bytes |
| Network payload limit | Bytes |
| Database index size | Usually bytes, verify database |
| Cursor movement | Grapheme clusters |
| Backspace behavior | Grapheme clusters |
| Encoding conversion | Code points and code units |
| API specification saying Unicode scalar values | Code points |
Choose the counting unit from the requirement, not from whichever length function is easiest to call. For debugging, inspect code points with Text to Unicode Code Points and compare strings with Unicode Text Compare. For notation details, read Unicode Escape Sequences Explained.
Common Unicode counting mistakes
Using JavaScript length as visible-character count
It counts UTF-16 code units.
Using spread syntax as grapheme count
It counts code points.
Using Python len() as byte count
It counts code points in normal strings.
Using PHP strlen() as character count
It counts bytes.
Using mb_strlen() as grapheme count
It is not equivalent to segmentation.
Assuming one emoji equals one code point
Many emoji are sequences.
Ignoring combining marks
Visible letters may use several code points.
Ignoring normalization
Equivalent text may have different counts.
Truncating UTF-8 at an arbitrary byte position
This can create invalid text.
Splitting surrogate pairs
This can produce invalid UTF-16 sequences.
Counting invisible characters as zero
Invisible code points still exist.
Applying only a client-side limit
Server-side validation must use the same unit and policy.
Practical counting workflow
- Define the product requirement.
- Decide whether the limit concerns display, storage or protocol.
- Preserve original text.
- Count grapheme clusters.
- Count code points.
- Count UTF-8 bytes.
- Count UTF-16 code units when relevant.
- Check normalization.
- Detect invisible characters.
- Validate both client and server.
- Test emoji and non-Latin scripts.
- Test truncation.
- Document the unit in the API and UI.
Use Unicode Character Counter, Unicode Sequence Analyzer, Byte Length Calculator and Unicode Character Inspector.
Try these UnicodeNow tools
These local tools help compare user-perceived characters, code points, encoded bytes and normalization details before you enforce a limit.
Unicode Character Counter
Count code points, grapheme clusters, words, bytes and invisible characters.
Unicode Sequence Analyzer
Analyze code points, grapheme clusters, bytes, scripts and directionality.
Unicode Character Inspector
Inspect each Unicode character, encoding, category, script and normalization form.
Byte Length Calculator
Count UTF-8 bytes, code points, grapheme clusters and UTF-16 code units for text.
UTF-8 Encoder and Decoder
Convert text to UTF-8 bytes and validate byte sequences.
Text to Unicode Code Points
Convert text into U+XXXX Unicode code point notation.
Unicode Normalization Checker
Check which Unicode normalization forms match the input.
Invisible Character Detector
Find zero-width, control, variation, private-use and spacing characters.
Unicode Text Compare
Compare strings exactly and after Unicode normalization.
Frequently asked questions
What does βUnicode character countβ mean?
It can mean bytes, code units, code points or grapheme clusters, so the unit must be specified.
What should I count for a visible character limit?
Grapheme clusters.
What does JavaScript String.length count?
UTF-16 code units.
Does JavaScript spread syntax count characters correctly?
It counts code points, not grapheme clusters.
What does Python len() count?
Code points in normal Python Unicode strings.
What does PHP strlen() count?
Bytes.
What does PHP mb_strlen() count?
Encoding-aware characters or code points, not necessarily grapheme clusters.
How many characters is an emoji?
It may be one grapheme cluster while containing one or several code points.
Why does π.length equal 2 in JavaScript?
Because the emoji uses two UTF-16 code units.
Why can Γ© have different lengths?
It may be one precomposed code point or a base letter plus a combining mark.
How do I count UTF-8 bytes?
Encode the string as UTF-8 and count the resulting bytes.
How do I count grapheme clusters in JavaScript?
Use Intl.Segmenter with granularity set to grapheme or a maintained fallback.
Should database limits use grapheme clusters?
User-facing limits often should, but database storage limits may still need byte validation.
Can normalization change string length?
Yes. It can change code-point and byte counts.
Can invisible characters increase length?
Yes. Invisible characters still occupy code points and encoded bytes.
Can I safely truncate by code points?
It preserves code points but may still split a grapheme cluster.
References
- The Unicode Standard
- Unicode glossary
- Unicode Standard Annex #29: Unicode Text Segmentation
- Unicode Standard Annex #15: Unicode Normalization Forms
- Unicode Character Database
- Unicode emoji data and technical report
- RFC 3629: UTF-8
- ECMAScript specification: String type
- MDN: Intl.Segmenter
- MDN: JavaScript String
- Python Unicode HOWTO
- Python regex package documentation
- PHP manual: Multibyte String
- PHP manual: grapheme_strlen
- ICU User Guide: Boundary Analysis