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

UnitWhat it countsExample for πŸ˜€Typical use
BytesEncoded storage units4 in UTF-8Storage, payload and file-size limits
UTF-16 code units16-bit encoding units2JavaScript indexes and string length
Code pointsUnicode scalar values1Unicode inspection and processing
Grapheme clustersUser-perceived characters1UI 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

TextGrapheme clustersCode pointsUTF-16 code unitsUTF-8 bytes
A1111
Γ©1112
é1223
πŸ˜€1124
πŸ‘πŸ½1248
πŸ‡ΊπŸ‡Έ1248
πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦1 where supportedMultipleMultipleMultiple

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.

Processed locally in your browser

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 bytes

UTF-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); // 2

UTF-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); // 2

Code-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 1

Grapheme 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.

FieldRecommended primary unit
Display nameGrapheme clusters
Chat messageGrapheme clusters plus byte limit
API tokenBytes or ASCII characters
UsernamePolicy-defined code points plus grapheme and byte checks
Database payloadBytes
PasswordProtocol-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.

GoalTruncate by
File or payload sizeBytes, preserving encoding boundaries
Unicode value limitCode points
User-visible previewGrapheme clusters
JavaScript API offsetUTF-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?

RequirementCorrect unit
Visible UI character limitGrapheme clusters
Unicode property validationCode points
JavaScript native string indexesUTF-16 code units
UTF-8 storage limitBytes
Network payload limitBytes
Database index sizeUsually bytes, verify database
Cursor movementGrapheme clusters
Backspace behaviorGrapheme clusters
Encoding conversionCode points and code units
API specification saying Unicode scalar valuesCode 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

  1. Define the product requirement.
  2. Decide whether the limit concerns display, storage or protocol.
  3. Preserve original text.
  4. Count grapheme clusters.
  5. Count code points.
  6. Count UTF-8 bytes.
  7. Count UTF-16 code units when relevant.
  8. Check normalization.
  9. Detect invisible characters.
  10. Validate both client and server.
  11. Test emoji and non-Latin scripts.
  12. Test truncation.
  13. 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.

Text ComparisonProcessed locally

Unicode Sequence Analyzer

Analyze code points, grapheme clusters, bytes, scripts and directionality.

UnicodeProcessed locally

Unicode Character Inspector

Inspect each Unicode character, encoding, category, script and normalization form.

UnicodeProcessed locally

Byte Length Calculator

Count UTF-8 bytes, code points, grapheme clusters and UTF-16 code units for text.

EncodingProcessed locally

Unicode Text Compare

Compare strings exactly and after Unicode normalization.

Text ComparisonProcessed locally

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