String.prototype.localeCompare()

The only correct way to sort text for people. Comparing strings with < orders them by UTF-16 code unit, which puts every capital before every lowercase letter and every accent after z.

String methodES3 (1999)Live demo
Common call
items.sort((a, b) => a.localeCompare(b))
Returns
a negative number, zero, or a positive number
Replaces
a < b comparison, which is wrong for text
Watch out
only the SIGN is specified — never compare against -1
string.localeCompare(other[, locales[, options]])
→ number

Demo

Live evaluation
Try:
Inputs
astringfirst string
bstringsecond string
Output
'a'.localeCompare('b')
-1

The sign is the whole answer: negative means the first string sorts first. The fourth case is the one that matters — 'a'.localeCompare('B') is negative, because collation understands that a comes before b regardless of case. Compare that with the < operator, where 'a' < 'B' is FALSE, because the code unit for B is 66 and for a is 97. That single difference is why a plain sort() puts Zebra before apple.

Parameters

NameTypeRequiredDescription
otherstringyesThe string to compare against.
localesstring | string[]no (host default)A BCP 47 language tag such as "de" or "sv". The ordering genuinely differs between languages.
optionsobjectno ({})Intl.Collator options — sensitivity, numeric, caseFirst, ignorePunctuation. sensitivity "base" makes the comparison ignore case and accents.

Return value

number — Negative if this string sorts before the other, positive if after, 0 if they compare equal. The exact magnitude is unspecified — only the sign is meaningful.

Common patterns

Sort an array of strings
The correct default for any user-visible list.
names.sort((a, b) => a.localeCompare(b));
Natural sort for numbered items
So item10 comes after item9.
files.sort((a, b) => a.localeCompare(b, undefined, {numeric: true}));
Reuse a collator when sorting a lot
Intl.Collator is much faster across many comparisons.
const c = new Intl.Collator();
names.sort(c.compare);

Examples

1. a before b
'a'.localeCompare('b')
Returns
-1
2. Case handled
'a'.localeCompare('B')
Returns
-1
3. The < operator is not
'a' < 'B'
Returns
false
4. German ä near a
'ä'.localeCompare('z', 'de')
Returns
-1
5. Swedish ä after z
'ä'.localeCompare('z', 'sv')
Returns
1
6. Numeric collation
'10'.localeCompare('9', undefined, {numeric: true})
Returns
1

Pitfalls

1. Plain sort() is lexicographic, not alphabetical
Array.prototype.sort with no comparator converts to strings and compares code units, so every capital letter sorts before every lowercase one and accented letters land after z. Any list a person will read needs a comparator.
Accents land after z
['zebra', 'äpfel', 'apple'].sort()
['apple', 'zebra', 'äpfel']
Collated
['zebra', 'äpfel', 'apple'].sort((a, b) => a.localeCompare(b))
['äpfel', 'apple', 'zebra']
2. Only the sign is specified
Implementations may return any negative or positive number, not just -1 and 1. Code that compares the result against -1 exactly will work in one engine and silently fail in another.
Assumes -1
if (a.localeCompare(b) === -1) { }
engine-dependent
Test the sign
if (a.localeCompare(b) < 0) { }
always correct
3. It is slow in a sort loop
Each call may construct a fresh collator. Sorting a few thousand strings this way is noticeably slower than building one Intl.Collator and reusing its compare method, which is the same algorithm without the setup.
Rebuilds each time
items.sort((a, b) => a.localeCompare(b))
fine for short lists
Reuse a collator
const c = new Intl.Collator();
items.sort(c.compare);
much faster
4. The order depends on the locale, by design
Swedish sorts ä after z; German sorts it with a. That is correct behaviour, not a bug — but it means the same array sorts differently for different users, so never store a locale-sorted order as if it were canonical.
Locale-dependent
'ä'.localeCompare('z', 'sv')
1
Fixed locale for storage
'ä'.localeCompare('z', 'de')
-1

When to use

Use it
  • Sorting any list a person will read
  • Case- and accent-insensitive comparison, via sensitivity options
  • Natural sorting of names containing numbers
  • Comparing text where the user locale should decide the order
Reach for something else
  • Sorting many thousands of items → Intl.Collator, reused
  • You only need equality of identical bytes → === after normalize
  • Sorting identifiers or keys → a plain comparison is fine and stable
  • You need a stable order across locales → pin the locale explicitly

Notes

Complexity
O(n) per comparison, with substantial constant cost from collation
Return
A number whose SIGN is meaningful; magnitude is unspecified
CPython impl
V8: Builtins-string-localecompare / ICU collator
Memory
May allocate a collator per call unless one is reused
Thread-safe
Single-threaded; the strings are only read

FAQ

Because that compares UTF-16 code units. Every capital letter has a lower number than every lowercase letter, so "Zebra" sorts before "apple", and accented characters sit above z entirely — a plain sort puts "äpfel" after "zebra". It is fast and correct for machine keys, and wrong for anything a human reads.

'a' < 'B';                  // false — B is code unit 66
'a'.localeCompare('B') < 0; // true — a comes first
['zebra', 'äpfel'].sort();  // ['zebra', 'äpfel']

History

ES3
localeCompare added with implementation-defined behaviour.
ES2012
ECMA-402 gave it the locales and options arguments, tying it to Intl.Collator.