Natural and locale-aware sorting

Intl.Collator with numeric: true

Baseline widely available
  • Chrome24
  • Edge12
  • Firefox29
  • Safari10

Features it needs

  • IntlWidely available

A plain sort() compares strings code unit by code unit, which puts "item10" before "item9" and files every accented word after z. Intl.Collator with numeric: true reads runs of digits as numbers and orders letters the way the locale expects. Passing collator.compare straight to sort() is also faster than a comparator that re-parses on every call, because the collator is built once.

When this applies

Sorting strings that contain numbers, or sorting for a human reader.

The native approach

const collator = new Intl.Collator("en", {
  numeric: true,
  sensitivity: "base",
});

["item10", "item9", "Item2"].sort(collator.compare);
// ["Item2", "item9", "item10"]

// Locale rules, not code unit order.
["Öl", "Oase", "Zebra"].sort(new Intl.Collator("de").compare);
// ["Oase", "Öl", "Zebra"]

MDN reference

When the dependency is still right

An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.

  • You need byte-identical output to the library's algorithm. Tie-breaking on case, punctuation and leading zeroes differs between implementations, so a snapshot test or a stored sort order will move.
  • The ordering has to match a server that sorts with a different collation, in which case the two need to agree on one algorithm rather than each picking its own.
  • You sort very large lists and measured Intl.Collator as too slow for the case. It is usually faster, but only when the collator is created once outside the sort.
  • You sort identifiers rather than text for people, where a stable code point order is the correct behaviour and locale rules would be wrong.
  • The package arrived as a direct dependency of your tooling rather than your application code, where removing it changes nothing that ships.

Packages this covers