String.prototype.normalize() returns the Unicode Normalization Form of a string (same API as MDN String.prototype.normalize()). Learn why lookalike strings can fail ===, the four forms (NFC, NFD, NFKC, NFKD), composed vs decomposed accents, compatibility ligatures, five examples, and try-it labs.
01
Kind
Instance method
02
Returns
New string
03
Default
NFC
04
Forms
NFC / NFD / NFKC / NFKD
05
Mutates?
No
06
Baseline
Widely available
Fundamentals
Introduction
Unicode sometimes has more than one way to store the same character. The letter "é" can be one code point, or "e" plus a combining accent. Both look like Amélie — but === says they are different, and even length can differ.
normalize() converts a string into a shared form so equivalent sequences compare equal. Pick a form once (usually "NFC") and use it before comparing, sorting keys, or storing text.
💡
Beginner tip
Normalize both sides before comparing: a.normalize() === b.normalize(). Normalizing only one side is a common bug.
NFKC / NFKD — compatibility equivalence (treat ligatures / fancy symbols alike for some apps).
C forms are composed; D forms are decomposed.
Invalid form — throws RangeError.
Foundation
📝 Syntax
General forms of String.prototype.normalize:
JavaScript
str.normalize()
str.normalize(form)
Parameters
form(optional) — "NFC", "NFD", "NFKC", or "NFKD". If omitted or undefined, "NFC" is used.
Return value
A string in the requested normalization form:
Form
Meaning
"NFC" (default)
Canonical Decomposition, then Canonical Composition
"NFD"
Canonical Decomposition (base + combining marks)
"NFKC"
Compatibility Decomposition, then Canonical Composition
"NFKD"
Compatibility Decomposition
Invalid form
Throws RangeError
Common patterns
JavaScript
const a = "\u00e9"; // é as one code point
const b = "e\u0301"; // e + combining acute
a === b; // false
a.normalize() === b.normalize(); // true (both NFC)
// Stable compare helper:
function sameText(x, y) {
return x.normalize("NFC") === y.normalize("NFC");
}
Cheat Sheet
⚡ Quick Reference
Goal
Code
Default (NFC)
str.normalize()
Compare safely
a.normalize() === b.normalize()
Decompose accents
str.normalize("NFD")
Compatibility (search)
str.normalize("NFKC")
Strip combining marks*
str.normalize("NFD").replace(/\p{M}+/gu, "")
Invalid form
RangeError
*Advanced pattern — requires Unicode property escapes in the regex.
Snapshot
🔍 At a Glance
Four facts to remember about String.normalize().
Returns
string
New normalized text
Default
NFC
Composed canonical
Best for
=== / keys
Before comparing
Mutates
no
Strings stay immutable
Compare
📋 normalize() vs localeCompare() vs raw ===
normalize()
===
localeCompare()
Purpose
Rewrite to a Unicode form
Exact code-unit equality
Locale-aware sort order
Fixes composed/decomposed?
Yes (after normalizing both)
No
Often treats them alike for sorting
Result
New string
Boolean
Number (<0 / 0 / >0)
Best for
Stable keys & equality
Exact same bytes/units
Human-facing sort
Hands-On
Examples Gallery
Examples follow MDN String.normalize() patterns. Use View Output or Try It Yourself for each case.
📚 Getting Started
See why lookalike strings differ — and how NFC fixes them.
Example 1 — Basic NFC Equality
MDN demo: two spellings of Amélie become equal after NFC.
Compatibility normalization is great for search (“find ff”), but it can change how text looks. Prefer NFC/NFD when you must preserve appearance.
Example 4 — All Four Forms on One String
MDN sample: long s with dots under different forms.
JavaScript
// U+1E9B LATIN SMALL LETTER LONG S WITH DOT ABOVE
// U+0323 COMBINING DOT BELOW
const str = "\u1E9B\u0323";
str.normalize("NFC"); // same as str.normalize()
str.normalize("NFD"); // long s + dot below + dot above
str.normalize("NFKC"); // single compatibility character U+1E69
str.normalize("NFKD"); // plain s + combining dots
// Inspect code points (hex):
[...str.normalize("NFD")].map((c) => c.codePointAt(0).toString(16));
// ["17f", "323", "307"]
str.normalize("NFKC").codePointAt(0).toString(16); // "1e69"
Typo-prone form names fail fast. Stick to the four uppercase strings from the Unicode / ECMAScript specs.
Applications
🚀 Common Use Cases
Equality checks — normalize both strings before ===.
Database / cache keys — store NFC so the same word always hashes the same.
Search indexing — NFKC/NFKD to fold ligatures and fancy symbols.
Form input cleanup — normalize user text on save.
Not a substitute for locale sort — still use localeCompare for language order.
Not the same as isWellFormed — that checks lone surrogates, not accents.
🧠 How normalize() Rewrites Text
1
Choose a form
Default NFC, or pass NFD / NFKC / NFKD.
Input
2
Decompose (and maybe recompose)
Canonical or compatibility rules expand and/or merge code points.
Transform
3
Produce a stable sequence
Equivalent inputs map to the same output for that form.
Unify
4
✅
Return a new string
Compare, store, or index the normalized result.
Important
📝 Notes
Default form is "NFC" when you omit the argument.
Normalize both sides before equality checks.
NFC/NFD preserve visual identity for canonical pairs; NFKC/NFKD may change appearance.
Invalid form throws RangeError.
This is unrelated to isWellFormed() / lone surrogates.
For human sort order across languages, still prefer localeCompare.
Compatibility
Browser & Runtime Support
String.prototype.normalize() is Baseline Widely available — supported across modern browsers since September 2016.
✓ Baseline · Widely available
String.normalize()
Safe for production text equality and storage keys. Prefer NFC unless you have a specific reason to use another form.
UniversalWidely available
Google ChromeSupported · Desktop & Mobile
Full support
Mozilla FirefoxSupported · Desktop & Mobile
Full support
Apple SafariSupported · macOS & iOS
Full support
Microsoft EdgeSupported · Chromium
Full support
Internet ExplorerNo native support · Use a polyfill
Polyfill
OperaSupported · Modern versions
Full support
Samsung InternetSupported · Android
Full support
BunSupported · JavaScript runtime
Supported
DenoSupported · JavaScript runtime
Supported
Node.jsSupported · Server runtime
Supported
Android WebViewSupported · Modern WebView
Full support
normalize()Excellent
Bottom line: Use String.normalize() to unify composed and decomposed Unicode text. Normalize both sides before comparing, and choose NFC for most equality and storage use cases.
Wrap Up
Conclusion
String.prototype.normalize() rewrites a string into a Unicode Normalization Form so equivalent characters compare and store consistently. Start with NFC, use NFD when you need decomposition, and reach for NFKC/NFKD only when compatibility folding is intentional.
Use NFKC for display when appearance must stay fancy
Confuse this with isWellFormed()
Pass typos like "nfc" / "XYZ" as the form
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about String.normalize()
Unicode forms that make equivalent text compare equal.
5
Core concepts
📝01
Returns
new string
API
🌐02
Default
NFC
Form
🔀03
Problem
composed ≠ decomposed
Unicode
⚡04
Compat
NFKC / NFKD
Search
📄05
Mutates
no
Immutable
❓ Frequently Asked Questions
String.prototype.normalize(form?) returns a new string in a Unicode Normalization Form (NFC, NFD, NFKC, or NFKD). The default form is NFC. It does not change the original string.
Unicode can represent the same character as one code point (composed) or as a base letter plus combining marks (decomposed). They look the same but have different code points and lengths until you normalize them.
Both are canonical forms. NFD decomposes characters into base + combining marks. NFC decomposes then recomposes into precomposed characters where possible. Use either consistently before comparing.
For compatibility equivalence — for example treating the ligature ff like "ff", or circled letters like plain letters. Useful for search; be careful for display because appearance can change.
normalize() throws RangeError if form is not one of "NFC", "NFD", "NFKC", or "NFKD".
No. Strings are immutable. normalize() returns a new normalized string.
Did you know?
File systems, browsers, and keyboards may emit different Unicode forms for the same typed accented letter. That is why a filename or username that “looks right” can still fail a strict === check until you normalize.