NFC vs NFD: What Is the Difference?
NFC and NFD are Unicode normalization forms that represent canonically equivalent text using different code-point sequences.
On this page
NFC vs NFD at a glance
| Feature | NFC | NFD |
|---|---|---|
| Full name | Normalization Form C | Normalization Form D |
| Decomposition | Canonical | Canonical |
| Composition afterward | Yes | No |
| Typical output | Composed where possible | Decomposed |
Example for é | U+00E9 | U+0065 U+0301 |
| Canonically equivalent | Yes | Yes |
| Compatibility mappings | No | No |
| Typical code-point count | Often lower | Often higher |
| General-purpose storage | Common choice | Used when decomposition is required |
| Visually identical output | Usually | Usually |
NFC favors composed canonical sequences. NFD favors decomposed canonical sequences. Both preserve canonical equivalence.
What is NFC?
NFC means Normalization Form C. It first performs canonical decomposition, then recomposes eligible sequences. In practice, it often replaces a base character plus combining mark with a precomposed character, such as e plus combining acute accent becoming é. It does not guarantee one code point per visible character, because many grapheme clusters have no single precomposed equivalent. It also preserves compatibility distinctions, so a ligature such as fi is not converted to ordinary fi.
Input:
U+0065 U+0301
NFC output:
U+00E9
Input:
U+0041 U+030A
NFC output:
U+00C5NFC is commonly suitable for application text and interchange. Compare real strings with the Unicode Normalizer or read the broader Unicode Normalization Explained guide.
What is NFD?
NFD means Normalization Form D. It performs canonical decomposition and does not recompose eligible sequences. Precomposed characters may become a base character plus one or more combining marks. Like NFC, NFD preserves compatibility distinctions. It may increase code-point and UTF-8 byte counts, but it remains canonically equivalent to the corresponding NFC text. NFD can appear in filesystem behavior, linguistic analysis and workflows that need combining marks as separate code points.
Input:
U+00E9
NFD output:
U+0065 U+0301
Input:
U+00C5
NFD output:
U+0041 U+030ANFD is not less valid or less correct than NFC. It is a different normalized representation.
How composition and decomposition work
Canonical decomposition
A precomposed character is replaced with its canonical component sequence when Unicode defines a decomposition mapping.
é → e + ◌́
U+00E9 → U+0065 U+0301Canonical ordering
Combining marks are reordered according to canonical combining classes when required. Developers normally rely on runtime normalization libraries rather than reproducing the full algorithm.
Canonical composition
Eligible decomposed sequences are recomposed into precomposed characters.
e + ◌́ → é
U+0065 U+0301 → U+00E9Composition exclusions and unavailable precomposed forms mean some sequences remain decomposed even after NFC.
NFC and NFD example with café
NFC
café
U+0063 U+0061 U+0066 U+00E9
UTF-8: 63 61 66 C3 A9NFD
café
U+0063 U+0061 U+0066 U+0065 U+0301
UTF-8: 63 61 66 65 CC 81| Property | NFC | NFD |
|---|---|---|
| Grapheme clusters | 4 | 4 |
| Code points | 4 | 5 |
| UTF-8 bytes | 5 | 6 |
| Visible text | café | café |
| Direct equality without normalization | May fail | May fail |
The rendered text may look identical despite different underlying data. Inspect the sequence with Unicode Character Inspector or Text to Unicode Code Points.
Interactive NFC and NFD comparison
This focused view reuses the same local normalization component used by the main normalization guide. It compares the original input with NFC and NFD, shows code points, grapheme counts and UTF-8 byte counts, and never sends entered text to the server.
NFC and NFD comparison
Compare composed and decomposed canonical forms side by side.
Limit: 2,000 UTF-16 code units. Text stays in your browser.
| Form | Rendered text | Code points | Code-point count | Grapheme clusters | UTF-8 bytes | Matches original? | Changed sequences |
|---|
Canonical equivalence
NFC and NFD forms can contain different code points while representing the same abstract text. U+00E9 and U+0065 U+0301 are canonically equivalent, but they are not byte-identical and are not raw-string-identical in most programming languages. Canonical equivalence is a Unicode relationship. Ordinary equality operators usually compare the actual stored sequence.
Canonical equivalence also does not imply compatibility equivalence. NFC and NFD do not convert compatibility characters such as ligatures, circled digits or full-width forms. Use NFKC or NFKD only under a documented policy. For the underlying text units, read What Is a Unicode Code Point? and Code Points vs Code Units.
NFC vs NFD code-point count
NFD often uses more code points because precomposed characters are decomposed. NFC may use fewer code points where composition is available. The difference is not universal: many characters have no decomposition, and some grapheme clusters remain multiple code points in NFC. Code-point count is not visible-character count.
| Text | NFC code points | NFD code points |
|---|---|---|
A | 1 | 1 |
é | 1 | 2 |
Å | 1 | 2 |
😀 | 1 | 1 |
👨👩👧👦 | Multiple | Multiple |
NFC vs NFD byte size
Normalization can change encoded byte count. NFC is often smaller for characters with precomposed forms, while NFD may require additional code points and bytes. This does not mean NFC is always smaller, and compression, markup and surrounding content affect actual file size. Storage size should be measured in the actual encoding.
é in NFC:
U+00E9
UTF-8: C3 A9
2 bytes
é in NFD:
U+0065 U+0301
UTF-8: 65 CC 81
3 bytesBoth forms may also require different sizes in UTF-16. Check bytes with the UTF-8 Encoder and Decoder and Byte Length Calculator.
NFC vs NFD string equality
const nfc = "caf\u00E9";
const nfd = "cafe\u0301";
console.log(nfc === nfd); // false
console.log(
nfc.normalize("NFC") === nfd.normalize("NFC")
); // trueRaw equality checks code-unit sequences. Normalize both operands to the same form; do not normalize only one value. Case-insensitive comparison, locale-aware collation and confusable-character checks are separate concerns. Use Unicode Text Compare or the Unicode Normalization Checker when debugging equality failures.
NFC and NFD are not encodings
NFC and NFD describe code-point normalization. UTF-8 and UTF-16 describe encoding. NFC text can be encoded as UTF-8 or UTF-16, and NFD text can also be encoded as UTF-8 or UTF-16. Converting UTF-16 to UTF-8 does not automatically change NFD to NFC, and normalizing text does not automatically change its character encoding.
Text
→ normalize as NFC or NFD
→ encode as UTF-8, UTF-16 or UTF-32
→ store or transmit bytesFor encoding background, read Unicode vs UTF-8 and UTF-8 vs UTF-16.
NFC and NFD do not remove accents
NFD may separate an accent into a combining mark, but it does not remove the mark. NFC may recombine the sequence. Accent removal requires an additional destructive operation, and not every accented character decomposes into a simple base Latin letter plus accent.
é
→ NFD
e + ◌́Removing U+0301 would produce e, but that operation is not Unicode normalization. It can change meaning, spelling and names.
NFC vs NFD and grapheme clusters
NFC and NFD can have different code-point counts while preserving the same grapheme-cluster count. A decomposed base-plus-mark sequence is normally one grapheme cluster. NFC does not guarantee one code point per grapheme cluster; emoji and complex writing systems can remain multi-code-point clusters.
é in NFC:
1 grapheme cluster
1 code point
é in NFD:
1 grapheme cluster
2 code pointsRead What Is a Grapheme Cluster?, then test text with Unicode Character Counter or Unicode Sequence Analyzer.
NFC and NFD in filenames
Filenames may look identical while using NFC or NFD-like sequences. Filesystems and operating systems can expose different normalization behavior, so applications must not assume every filename is stored in NFC. Raw string comparisons may fail, copying files between systems may reveal differences, and automatic renaming can create collisions.
résumé.txtEach accented é might be represented in composed or decomposed form. File operations should preserve the actual filesystem-provided path, while display and comparison logic may use a separate normalized key.
NFC and NFD in databases
Databases may store NFC and NFD strings as distinct byte sequences. Unique constraints may or may not treat them as equivalent depending on collation, engine and configuration. Normalizing comparison keys can improve consistency, but original display text may need to be preserved separately. Existing data should be audited before introducing normalization, and indexes and unique constraints must use a consistent policy.
display_value
normalized_comparison_valueNormalization does not replace collation design. All writes and comparisons must apply the same rule.
NFC and NFD in search
Search can miss canonically equivalent text if input and indexed data use different forms. Normalizing both indexed content and queries can improve matching. NFC is often a practical canonical form, but search systems should preserve original text for display. Accent-insensitive matching, case-insensitive matching and language-specific search are separate features.
Original text → preserve for display
NFC search key → use for canonical matchingNFD can also be a valid search representation when the product deliberately wants decomposed canonical components.
NFC and NFD in URLs and slugs
URL encoding converts bytes, not normalization forms. NFC and NFD can produce different UTF-8 byte sequences, which produce different percent-encoded paths. Slug generation should define one normalization form, canonical URLs should use a consistent policy, and existing URLs should not be silently changed without redirects.
Normalize
→ apply slug rules
→ encode URL componentsOriginal titles can be preserved while normalized values generate slugs. Use the URL Encoder and Decoder to inspect encoded URL components.
NFC and NFD in APIs
JSON strings can contain NFC or NFD text. Two values that look identical may fail direct equality, so API contracts should document normalization-sensitive identifiers. Normalization can be applied at defined boundaries, and producers and consumers must use compatible policies. Digital signatures and hashes are sensitive to byte differences, so do not normalize signed payloads unless the protocol defines it.
NFC vs NFD in JavaScript
const composed = "caf\u00E9";
const decomposed = "cafe\u0301";
console.log(composed.normalize("NFC"));
console.log(decomposed.normalize("NFC"));
console.log(composed.normalize("NFD"));
console.log(decomposed.normalize("NFD"));function isNFC(text) {
return text === text.normalize("NFC");
}
function isNFD(text) {
return text === text.normalize("NFD");
}
function canonicallyEqual(first, second) {
return first.normalize("NFC") === second.normalize("NFC");
}normalize() returns a new string and leaves the original unchanged. Invalid form names throw an error. JavaScript equality does not automatically normalize, string length still counts UTF-16 code units, and NFC comparison is not case-insensitive comparison.
NFC vs NFD in Python
import unicodedata
composed = "caf\u00E9"
decomposed = "cafe\u0301"
nfc = unicodedata.normalize("NFC", decomposed)
nfd = unicodedata.normalize("NFD", composed)def is_nfc(text: str) -> bool:
return text == unicodedata.normalize("NFC", text)
def is_nfd(text: str) -> bool:
return text == unicodedata.normalize("NFD", text)
def canonically_equal(first: str, second: str) -> bool:
return (
unicodedata.normalize("NFC", first)
== unicodedata.normalize("NFC", second)
)Python strings represent Unicode text. Normalization changes the code-point sequence; encoding to UTF-8 is a separate operation. The Unicode data version follows the Python runtime, so tests should use the actual runtime data.
NFC vs NFD in PHP
$composed = "caf\u{00E9}";
$decomposed = "cafe\u{0301}";
$nfc = Normalizer::normalize(
$decomposed,
Normalizer::FORM_C
);
$nfd = Normalizer::normalize(
$composed,
Normalizer::FORM_D
);$isNfc = Normalizer::isNormalized(
$composed,
Normalizer::FORM_C
);
$isNfd = Normalizer::isNormalized(
$decomposed,
Normalizer::FORM_D
);function canonicallyEqual(
string $first,
string $second
): bool {
$normalizedFirst = Normalizer::normalize($first, Normalizer::FORM_C);
$normalizedSecond = Normalizer::normalize($second, Normalizer::FORM_C);
if ($normalizedFirst === false || $normalizedSecond === false) {
throw new RuntimeException("Unicode normalization failed.");
}
return $normalizedFirst === $normalizedSecond;
}The Intl extension is required, input should be valid UTF-8 when expected, and Normalizer::normalize() may return false. PHP strings remain byte sequences, and NFC/NFD normalization is separate from mb_strlen() and grapheme operations.
When should you use NFC?
NFC is a likely starting point for general application text, web content, API comparison keys, search indexing, slug generation, database comparison values, deduplication, canonical equality checks and mixed-source imports. It is often the most practical canonical form for general-purpose text, but the application must define the policy explicitly.
NFC is not mandatory and should not automatically replace original text when exact representation matters.
When might NFD be useful?
NFD can be useful for linguistic analysis, processing combining marks separately, accent-related transformations, systems or file representations that expose decomposed text, algorithms requiring canonical decomposition, comparing canonical components and specialized text-processing pipelines.
NFD is useful when the application needs canonical components explicitly, not because it is universally more correct.
When should original text be preserved?
Preserve original text for legal or archival records, digital signatures, cryptographic hashes, exact user input, source-code analysis, forensic data, filesystem paths, external identifiers and synchronized data. A common pattern is to keep the source value and separately compute a normalized value for a defined comparison or indexing purpose.
Original value → preserve
Normalized value → use for defined comparison or indexingNever normalize bytes after signing or hashing unless the protocol explicitly requires normalization first.
Common NFC and NFD mistakes
Assuming identical rendering means identical data
Code-point and byte sequences can differ.
Normalizing only one side of a comparison
Both sides need the same policy.
Assuming NFC produces one code point per character
Many grapheme clusters remain multi-code-point.
Assuming NFD removes accents
It decomposes many characters but does not remove combining marks.
Treating NFC as UTF-8
Normalization form and encoding are separate.
Treating NFD as broken text
NFD is a valid normalized representation.
Replacing original text without a reason
Preserve source data where fidelity matters.
Ignoring filename collisions
Normalization can make distinct raw names compare equally.
Normalizing after hashing or signing
Byte differences matter.
Using NFC as a security filter
It does not detect confusable scripts or invisible controls.
Assuming all operating systems use one normalization form
Filesystem behavior varies.
Confusing canonical equivalence with compatibility equivalence
NFC and NFD do not convert compatibility characters such as ligatures to ordinary letters.
Practical NFC/NFD workflow
- Preserve the original value when fidelity matters.
- Confirm that the input is valid Unicode text.
- Identify whether the operation is display, comparison, indexing or storage.
- Choose NFC or NFD deliberately.
- Normalize all compared values consistently.
- Handle case folding separately when required.
- Handle accent-insensitive behavior separately.
- Detect invisible and confusable characters separately.
- Store a normalized comparison key when appropriate.
- Test precomposed accents, combining marks, emoji and non-Latin scripts.
Use Unicode Normalizer, Unicode Normalization Checker, Unicode Text Compare and Unicode Character Inspector.
Which should you choose?
| Requirement | Likely choice |
|---|---|
| General web and application text | NFC |
| Canonical comparison key | Usually NFC |
| Decomposed character analysis | NFD |
| Processing combining marks | NFD may help |
| Exact source preservation | Preserve original |
| Filesystem operations | Preserve actual path representation |
| Search indexing | Often NFC, according to product policy |
| Digital signatures | Follow the protocol exactly |
| Compatibility-insensitive comparison | Neither alone; consider NFKC under a documented policy |
Choose NFC as the usual general-purpose canonical representation. Choose NFD when your processing explicitly requires decomposed canonical sequences. This is a practical starting point, not a universal rule.
Try these UnicodeNow tools
These tools help inspect normalization forms, byte counts, code points, invisible characters and equality behavior.
Unicode Normalizer
Normalize Unicode text to NFC, NFD, NFKC or NFKD.
Unicode Normalization Checker
Check which Unicode normalization forms match the input.
Unicode Text Compare
Compare strings exactly and after Unicode normalization.
Unicode Character Inspector
Inspect each Unicode character, encoding, category, script and normalization form.
Unicode Sequence Analyzer
Analyze code points, grapheme clusters, bytes, scripts and directionality.
Text to Unicode Code Points
Convert text into U+XXXX Unicode code point notation.
Unicode Character Counter
Count code points, grapheme clusters, words, bytes and invisible characters.
UTF-8 Encoder and Decoder
Convert text to UTF-8 bytes and validate byte sequences.
Invisible Character Detector
Find zero-width, control, variation, private-use and spacing characters.
Frequently asked questions
What is the difference between NFC and NFD?
NFC recomposes canonical sequences where possible, while NFD keeps them canonically decomposed.
Are NFC and NFD visually identical?
They often render identically, but their code-point and byte sequences may differ.
Is NFC better than NFD?
Not universally. NFC is a common general-purpose form; NFD is useful when decomposed canonical sequences are required.
Is NFC the same as UTF-8?
No. NFC is a normalization form; UTF-8 is a character encoding.
Does NFD remove accents?
No. It may separate accents into combining marks, but it does not delete them.
Does NFC always use fewer code points?
Often for characters with precomposed forms, but not for every string.
Can NFC and NFD strings fail equality comparison?
Yes. Raw equality normally compares the stored sequence.
How can I compare NFC and NFD strings safely?
Normalize both values to the same form before comparison.
Does NFC make one code point per visible character?
No. Emoji, combining sequences and many scripts may remain multi-code-point grapheme clusters.
Can NFC and NFD have different UTF-8 byte lengths?
Yes. Decomposed text may require additional code points and bytes.
Should databases store NFC?
NFC is often a practical choice, but database collation, legacy data and application requirements must be considered.
Do filesystems always use NFD?
No. Filesystem and operating-system behavior varies, and applications should preserve actual filenames.
Can normalization change hashes or signatures?
Yes. Different normalized forms may produce different encoded bytes.
Does NFC prevent Unicode spoofing?
No. Confusables, mixed scripts and invisible controls require separate validation.
References
- The Unicode Standard
- Unicode Standard Annex #15: Unicode Normalization Forms
- Unicode normalization test data
- Unicode Character Database
- Unicode glossary
- Unicode Standard Annex #29: Unicode Text Segmentation
- MDN: String.prototype.normalize()
- Python documentation: unicodedata
- PHP manual: Normalizer
- ICU normalization documentation