String.prototype.toLowerCase()

The workhorse of case-insensitive comparison. It uses locale-independent Unicode rules, which is usually what you want — and is exactly wrong for Turkish.

String methodES1 (1997)Live demo
Common call
a.toLowerCase() === b.toLowerCase()
Returns
a new string — assign it
Replaces
nothing; it is the base case-folding method
Watch out
locale-independent by design; use toLocaleLowerCase for display
string.toLowerCase()
→ string

Demo

Live evaluation
Try:
Inputs
sstringtext to lowercase
Output
'Hello World'.toLowerCase()
'hello world'

Accented capitals map to their accented lower-case forms, and anything without a case — digits, punctuation, spaces — passes through untouched. The fourth case looks unremarkable and is the one to think about: this method uses the LOCALE-INDEPENDENT Unicode mapping, so a capital I always becomes a dotted i. In Turkish that is the wrong letter, and toLocaleLowerCase('tr') produces the dotless ı instead.

Common patterns

Case-insensitive comparison
Fold both sides, never just one.
if (a.toLowerCase() === b.toLowerCase()) { }
Normalise a key or slug
Trim and fold before storing.
const key = raw.trim().toLowerCase();
Display in the user locale
The locale-aware variant, for text people read.
const shown = label.toLocaleLowerCase(navigator.language);

Examples

1. Mixed case
'Hello'.toLowerCase()
Returns
'hello'
2. Already lower
'abc'.toLowerCase()
Returns
'abc'
3. Digits unchanged
'ABC123'.toLowerCase()
Returns
'abc123'
4. Locale-independent
'I'.toLowerCase()
Returns
'i'
5. Turkish differs
'I'.toLocaleLowerCase('tr')
Returns
'ı' // dotless
6. Length can change
'\u0130'.toLowerCase().length
Returns
2

Pitfalls

1. It returns a new string and changes nothing
The same immutability rule as every string method, and worth stating because case conversion feels like something that ought to happen in place.
Discarded
let s = 'ABC';
s.toLowerCase();
s
'ABC'
Assign it
let s = 'ABC';
s = s.toLowerCase();
s
'abc'
2. Case folding is not accent folding
Lowercasing maps É to é, not to e. A search that should treat "elan" and "élan" as the same needs normalisation and diacritic removal as a separate step.
Still differs
'Élan'.toLowerCase() === 'elan'
false
Strip marks too
'Élan'.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '') === 'elan'
true
3. The Turkish dotless i
The canonical reason toLocaleLowerCase exists. In Turkish and Azeri, capital I lowercases to ı and capital İ lowercases to i. Using the locale-aware version for a protocol comparison is the mistake — an identifier folded under a Turkish locale stops matching its ASCII form.
Locale-dependent
'ID'.toLocaleLowerCase('tr') === 'id'
false
Locale-independent
'ID'.toLowerCase() === 'id'
true
4. Round-tripping case is lossy
Some characters change length or identity on conversion — the German ß uppercases to two characters, SS, which lowercases back to ss and not ß. Never assume upper-then-lower returns the original.
Not reversible
'\u00df'.toUpperCase().toLowerCase()
'ss' // was 'ß'
Keep the original
const display = original;
'ß'

When to use

Use it
  • Case-insensitive comparison and lookup keys
  • Normalising identifiers, protocols, header names and file extensions
  • Search filtering, alongside trim
Reach for something else
  • Text the user will read → toLocaleLowerCase with their locale
  • Accent-insensitive matching → normalize and strip diacritics as well
  • Sorting → localeCompare, which handles case and accents properly
  • You want upper case → toUpperCase

Notes

Complexity
O(n)
Return
A new string; the original is untouched
CPython impl
V8: Builtins-string-tolowercase / ICU
Memory
Allocates the result; the length may differ from the input
Thread-safe
Single-threaded; the string is only read

FAQ

toLowerCase for anything a machine compares — keys, identifiers, protocols, extensions — because it is locale-independent and therefore stable. toLocaleLowerCase for text a person reads, where the user locale should decide. Using the locale version for comparisons is how the Turkish-i bug gets in.

key.toLowerCase();                      // comparison
label.toLocaleLowerCase(userLocale);    // display

History

ES1
toLowerCase and toUpperCase present from the first version.
ES3
toLocaleLowerCase and toLocaleUpperCase added for locale-sensitive mapping.
ES2017
The locale variants gained an explicit locale argument rather than relying on the host default.