Why toLowerCase() Breaks Case-Insensitive Comparison: Unicode Case Folding
'straße' and 'STRASSE' differ after toLowerCase(). Case conversion vs case folding, the Turkish i, simple folding in regex /iu, and normalization, per Unicode 17.0 and ECMAScript 2026.
A common check:
a.toLowerCase() === b.toLowerCase()
With straße and STRASSE it returns false: ß is already lowercase, so it stays. Uppercase both and ß becomes SS; they match. A direction-dependent result is unsuitable for comparison.
Case folding
Unicode §3.13 separates case conversion from folding for caseless matching. Default case folding uses the full mappings in CaseFolding.txt (status C and F), where ß folds to ss. Python’s 'straße'.casefold() gives 'strasse'. ECMAScript has no built-in String.prototype method for full default Unicode case folding.
The Turkish i
Turkish and Azeri pair ı with I and i with İ (tr and az rules in SpecialCasing.txt). Java’s toLowerCase() uses the default locale; its docs warn that a Turkish locale turns "TITLE" into "tıtle" and recommend Locale.ROOT for identifiers and protocol keys. JavaScript’s toLowerCase() ignores locale; toLocaleLowerCase("tr") gives "tıtle".
Regex /iu
With i plus u or v, ECMAScript maps each character through the simple or common mappings in CaseFolding.txt (status S and C). Each maps to one code point, so ß stays ß, while the Kelvin sign K (U+212A) folds to k. So /^straße$/iu.test("STRASSE") is false and /^k$/iu.test("\u212A") is true.
Normalization
Å (U+00C5) and A + U+030A fold to å and a + U+030A; folding doesn’t preserve normalization forms. Unicode’s canonical caseless match (D145) compares NFD(toCasefold(NFD(X))) on both sides. See NFC and NFD in Why character counts differ between tools.
Unicode §5.18 describes storing a folded copy next to the original for comparison. Examples ran on Node.js 24.13.0 and Python 3.14.7 (Unicode 16.0 data; same mappings in 17.0).
Sources
- Unicode 17.0, §3.13 Default Case Algorithms
- Unicode 17.0, §5.18 Case Mappings
- CaseFolding.txt 17.0.0, SpecialCasing.txt 17.0.0
- ECMA-262 2026 (17th edition): 22.1.3.26, 22.1.3.28, 22.2.2.7.3
- ECMA-402 2026 (13th edition): 20.1.2.1
- Java SE 27
String.toLowerCase() - Python 3.14
str.casefold()
Links and content were checked on 7 October 2026.