Unicode normalization
Also known as: Unicode normalisation, NFC, NFD, NFKC, NFKD, UAX #15, Unicode equivalence
Unicode normalization rewrites text into one standard sequence of code points, so strings that look identical — é stored as a single precomposed character, or as e followed by a combining accent — compare as equal. Unicode defines four normalization forms: NFC, NFD, NFKC and NFKD.
Overview
Unicode often has more than one way to write the same thing. é can be the single code point U+00E9, or U+0065 followed by the combining acute accent U+0301. The two render identically and mean the same, but they are different bytes, so a plain string comparison, a hash, a database lookup or a domain-name conversion treats them as different strings. Normalization picks one representation so that equal text is also equal data.
Unicode recognises two kinds of sameness, and the four forms come from combining them with a direction. Canonical equivalence covers genuinely identical characters encoded differently: precomposed é and e plus its accent, the Ångström sign U+212B and the letter Å, a Hangul syllable like 한 and its three component jamo. NFD decomposes everything into base characters and combining marks; NFC decomposes and then recomposes wherever a precomposed character exists, which makes it the shorter form, the one most text is already in, and the default for JavaScript's normalize(). Compatibility equivalence is looser: it also equates characters that differ only in formatting, such as the fi ligature and 'fi', fullwidth A and A, superscript ² and 2, or circled ① and 1. NFKD and NFKC fold those as well, which is why they suit matching rather than storage — x² becomes x2, and nothing can turn it back.
The forms show up wherever text is compared. Domain names: UTS #46 maps a name and then normalises it to NFC, so a decomposed café still reaches the same domain as a precomposed one. URLs: percent-encoding the two spellings gives caf%C3%A9 and cafe%CC%81, two different paths to a server that does not normalise. Filenames: HFS+, the Mac file system until 2017, stored every name decomposed and APFS keeps whichever form a name was created in, so a file copied off an older Mac may not match the same name typed elsewhere. Identifiers: Python normalises identifiers with NFKC, so a variable written with the fi ligature is the same variable as 'file'. Passwords and usernames: RFC 8265, the PRECIS profile for both, requires NFC; its predecessor SASLprep used NFKC.
Normalization changes length, which catches out validation code. é is one code point in NFC and two in NFD, and NFKC can expand a single character dramatically — U+FDFA, an Arabic ligature, becomes 18 characters. Normalise first, then measure, truncate or enforce limits. The results are stable across versions: Unicode's stability policy guarantees that a string normalised under one version of the standard stays normalised under every later one, so normalised data never has to be migrated when Unicode is updated.
Common questions about Unicode normalization
What is the difference between NFC and NFD?
When should I use NFKC instead of NFC?
Why don't two identical-looking strings match?
Does normalization change the length of a string?
Should passwords be normalised before hashing?
Tools that work with Unicode normalization
Related concepts
IDN
An IDN (Internationalized Domain Name) is a domain name containing characters outside ASCII, which is carried through DNS as an ASCII xn-- form produced by the IDNA processing defined in UTS #46.
Punycode
Punycode is the encoding defined by RFC 3492 that represents a Unicode string using only the letters, digits and hyphen that DNS allows, producing the xn-- labels that carry internationalised domain names.
Homograph attack
A homograph attack uses characters that look identical to others — Cyrillic а in place of Latin a — so that a different domain, username or identifier appears to be a trusted one. Browsers defend domain names by showing the xn-- form of names that look suspicious instead of the Unicode spelling.