Unicode Normalization Explained: NFC, NFD, NFKC, and NFKD With Practical Examples
UnicodeText ProcessingJavaScriptPythonDatabases

Unicode Normalization Explained: NFC, NFD, NFKC, and NFKD With Practical Examples

UUnicode.live Editorial Team
2026-08-07
6 min read

Learn when to use NFC, NFD, NFKC, or NFKD to compare, store, validate, and transform Unicode strings safely.

Unicode normalization gives developers a consistent way to represent equivalent text, but the four forms are not interchangeable. This guide explains NFC, NFD, NFKC, and NFKD, shows how they differ in JavaScript and Python, and provides a practical method for choosing a form for comparison, search, storage, APIs, identifiers, and user-facing text.

Overview

Unicode can represent the same visible text in more than one sequence of code points. For example, “é” can be stored as a single precomposed character, U+00E9 LATIN SMALL LETTER E WITH ACUTE, or as U+0065 LATIN SMALL LETTER E followed by U+0301 COMBINING ACUTE ACCENT. They usually render identically, but a direct code-point comparison can report that they are different.

Unicode normalization transforms text into a predictable representation. The four standard forms are divided into two groups:

  • NFC uses canonical decomposition followed by canonical composition. It generally produces composed characters where a canonical equivalent exists.
  • NFD uses canonical decomposition. Characters are separated into their canonical component sequences.
  • NFKC applies compatibility decomposition and then canonical composition. It can convert some formatting distinctions into ordinary text.
  • NFKD applies compatibility decomposition without recomposing the result.

Canonical equivalence preserves the intended textual identity. Compatibility equivalence is broader: it treats some presentation or formatting variants as equivalent for particular processing tasks. A compatibility transformation may change distinctions that matter to display, search, identifiers, or domain-specific data. That is why “normalize Unicode strings” is not a single universal operation.

Normalization also differs from encoding. UTF-8 converts code points into bytes for transport or storage; normalization changes the code-point sequence before or after that encoding step. It is also different from case folding, transliteration, accent removal, and confusable detection. Those operations may be useful alongside normalization, but they solve different problems.

How to compare options

Choose a normalization form by first defining what “equivalent” means in the application. A reliable comparison should answer four questions.

  1. Is the text user-facing? Preserve the user’s intended appearance whenever possible. Avoid compatibility normalization merely to make strings easier to compare.
  2. Is the text being compared or searched? Normalize both values using the same form before comparison. If search should ignore case or accents, specify those rules separately rather than assuming normalization does so.
  3. Is the value an identifier? Usernames, keys, filenames, slugs, and account references require an explicit policy. Normalization can reduce accidental mismatches, but it does not resolve confusable characters or define a complete security policy.
  4. Must the original representation be retained? If exact reproduction, legal text, signatures, audit records, or round-trip fidelity matter, preserve the original and treat any normalized form as a derived value.

For many ordinary text workflows, NFC is a sensible default because it preserves canonical equivalence while producing a compact, widely interoperable representation. NFD can be useful when an algorithm needs base characters and combining marks separately, such as controlled accent analysis. NFKC and NFKD are better treated as deliberate compatibility transforms, not automatic cleanup steps.

When testing an implementation, compare more than what appears on screen. Inspect code points, test precomposed and decomposed input, include combining marks, and verify behavior at system boundaries. The Unicode text QA checklist for web releases can help organize those checks.

Feature-by-feature breakdown

NFC: canonical composition

NFC turns canonically equivalent sequences into a composed form where Unicode defines one. The strings “café” and “café” become comparable after both are normalized to NFC. NFC is often appropriate for database values, JSON fields, form input, and text exchanged between services when the goal is consistent canonical representation.

NFC does not remove accents, convert scripts, translate characters, or make visually similar characters identical. It also does not generally turn compatibility characters such as fullwidth Latin letters into ASCII.

NFD: canonical decomposition

NFD separates characters into their canonical components. The character “é” becomes “e” plus a combining acute accent. This can make combining-mark processing easier, but it may increase the number of code points and can expose assumptions in code that counts characters by code unit or code point.

A common accent-insensitive search pattern is to apply NFD and then remove combining marks. That is a separate lossy transformation, not what NFD itself does. Use it only when the product requirement explicitly says that those distinctions should be ignored.

NFKC: compatibility composition

NFKC goes beyond canonical equivalence. It may convert compatibility forms such as fullwidth letters or certain presentation characters into more ordinary equivalents, then compose the result where possible. This can improve matching of loosely formatted input, but it may also erase distinctions that a user or domain considers meaningful.

Do not apply NFKC blindly to passwords, signed content, source text, or identifiers whose exact characters carry meaning. Document the choice and test representative data before making it part of an API contract.

NFKD: compatibility decomposition

NFKD exposes compatibility decompositions without recomposing them. It is useful as an intermediate representation for specialized pipelines, such as controlled search folding or transliteration preparation. Because it can significantly alter presentation distinctions, it should normally be followed by an explicitly defined processing step rather than stored as the user’s original text.

JavaScript and Python examples

Modern JavaScript provides normalization through String.prototype.normalize():

const composed = "café";
const decomposed = "cafe\u0301";

composed === decomposed; // false
composed.normalize("NFC") === decomposed.normalize("NFC"); // true

const compatibility = "Fullwidth";
const ordinary = compatibility.normalize("NFKC");

Use a supported form string—NFC, NFD, NFKC, or NFKD—and normalize both operands before comparing. In Python, the standard library exposes the same four forms through unicodedata.normalize():

import unicodedata

composed = "café"
decomposed = "cafe\u0301"

same = (
    unicodedata.normalize("NFC", composed)
    == unicodedata.normalize("NFC", decomposed)
)

folded = unicodedata.normalize("NFKC", "Fullwidth")

Do not rely on visual inspection alone. Browser-based Unicode tools can help inspect characters and code points; the guide to comparing browser-based Unicode tools provides a workflow for quick investigations.

Best fit by scenario

  • General text storage and API interchange: Consider NFC when your systems need a consistent canonical form. State the policy in the API or data contract and apply it consistently at agreed boundaries.
  • Exact user content: Preserve the original input. If normalized comparison is needed, create a separate comparison value rather than silently replacing the displayed text.
  • Accent-aware or accent-insensitive search: Start with the product’s language requirements. NFD followed by carefully scoped mark handling may be useful, but do not assume that removing marks is correct for every language.
  • Loose matching of formatting variants: Consider NFKC when compatibility differences should not affect matching. Test symbols, measurements, mathematical text, and domain-specific identifiers before adopting it.
  • Security-sensitive identifiers: Normalization is only one layer. Add validation, script restrictions where appropriate, case policy, and confusable analysis. The Unicode confusables checker guide covers a separate but related risk.
  • Slugs and URLs: Decide whether the application keeps Unicode or generates an ASCII form. Normalization alone is not transliteration or slugification; see the guide to multilingual URL slug generation.
  • Forms and JSON APIs: Normalize at a documented boundary, validate the resulting value, and test serialization and database round trips. The Unicode API and form validation guide is a useful companion.

When to revisit

Revisit a normalization policy whenever a new integration, database, search feature, authentication flow, or language requirement changes how text is processed. Changes to runtime libraries, database collations, input validation, URL generation, or client-side form handling can expose mismatches that were previously hidden.

Make the review practical. Keep test fixtures containing precomposed and decomposed accents, combining marks, fullwidth characters, ligatures, multiple scripts, unusual whitespace, and representative identifiers. Compare values before and after normalization, verify that stored data can be read back correctly, and check whether logs, caches, indexes, signatures, and API comparisons use the same policy.

As a starting checklist: use NFC for ordinary canonical consistency unless requirements say otherwise; reserve NFKC and NFKD for documented compatibility workflows; never confuse normalization with security validation; preserve original text when fidelity matters; and normalize both sides of a comparison. For related edge cases, review the guide to normalizing and comparing user input across languages and the Unicode whitespace testing guide. A small, explicit policy is easier to test and safer to maintain than an invisible cleanup rule applied everywhere.

Related Topics

#Unicode#Text Processing#JavaScript#Python#Databases
U

Unicode.live Editorial Team

Technical Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.