How to sort strings alphabetically by locale in JavaScript
Use Intl.Collator and localeCompare() to sort strings correctly for any language
Introduction
When you sort an array of strings in JavaScript, the default behavior compares strings by their UTF-16 code unit values. This works for basic ASCII text, but fails when sorting names, product titles, or any text containing accented characters, non-Latin scripts, or mixed case letters.
Different languages have different rules for alphabetical order. Swedish places å, ä, and ö at the end of the alphabet after z. German treats ä as equivalent to a in most contexts. French ignores accents in certain comparison modes. These linguistic rules determine how people expect to see sorted lists in their language.
JavaScript provides two APIs for locale-aware string sorting. The String.prototype.localeCompare() method handles simple comparisons. The Intl.Collator API offers better performance when sorting large arrays. This lesson explains how both work, when to use each one, and how to configure sorting behavior for different languages.
Why default sort fails for international text
The default Array.sort() method compares strings by their UTF-16 code unit values. This means uppercase letters always come before lowercase letters, and accented characters sort after z.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort();
console.log(sorted);
// Output: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
This output is wrong for Swedish. In Swedish, å, ä, and ö are separate letters that belong at the end of the alphabet. The correct order should place Anna first, then Bengt, then Åsa, Ärla, and Örjan.
The problem occurs because the default sort compares code point values, not linguistic meaning. The letter Å has code point U+00C5, which is greater than the code point for z (U+007A). JavaScript has no way to know that Swedish speakers consider Å a separate letter with a specific position in the alphabet.
Mixed case creates another problem.
const words = ['zebra', 'Apple', 'banana', 'Zoo'];
const sorted = words.sort();
console.log(sorted);
// Output: ['Apple', 'Zoo', 'banana', 'zebra']
All uppercase letters have lower code point values than lowercase letters. This causes Apple and Zoo to appear before banana, which is not alphabetical order in any language.
How localeCompare sorts strings by linguistic rules
The localeCompare() method compares two strings according to the sort order rules of a specific locale. It returns a negative number if the first string comes before the second, zero if they are equivalent, and a positive number if the first string comes after the second.
const result = 'a'.localeCompare('b', 'en-US');
console.log(result);
// Output: -1 (negative means 'a' comes before 'b')
You can use localeCompare() directly with Array.sort() by passing it as the comparison function.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(sorted);
// Output: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
The Swedish locale places Anna and Bengt first because they use standard Latin letters. Then come Åsa, Ärla, and Örjan with their special Swedish letters at the end.
The same list sorted with a German locale produces different results.
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(sorted);
// Output: ['Anna', 'Ärla', 'Åsa', 'Bengt', 'Örjan']
German treats ä as equivalent to a for sorting purposes. This places Ärla right after Anna, instead of at the end like Swedish does.
When to use localeCompare
Use localeCompare() when you need to sort a small array or compare two strings. It provides a simple API without requiring you to create and manage a collator object.
const items = ['Banana', 'apple', 'Cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
console.log(sorted);
// Output: ['apple', 'Banana', 'Cherry']
This approach works well for arrays with a few dozen items. The performance impact is negligible for small datasets.
You can also use localeCompare() to check if one string comes before another without sorting an entire array.
const firstName = 'Åsa';
const secondName = 'Anna';
if (firstName.localeCompare(secondName, 'sv-SE') < 0) {
console.log(`${firstName} comes before ${secondName}`);
} else {
console.log(`${secondName} comes before ${firstName}`);
}
// Output: "Anna comes before Åsa"
This comparison respects Swedish alphabetical order without needing to sort a full array.
How Intl.Collator improves performance
The Intl.Collator API creates a reusable comparison function optimized for repeated use. When you sort large arrays or perform many comparisons, a collator is significantly faster than calling localeCompare() for each comparison.
const collator = new Intl.Collator('sv-SE');
const names = ['Åsa', 'Anna', 'Örjan', 'Bengt', 'Ärla'];
const sorted = names.sort(collator.compare);
console.log(sorted);
// Output: ['Anna', 'Bengt', 'Åsa', 'Ärla', 'Örjan']
The collator.compare property returns a comparison function that works directly with Array.sort(). You do not need to wrap it in an arrow function.
Creating a collator once and reusing it for multiple operations avoids the overhead of looking up locale data on every comparison.
const collator = new Intl.Collator('de-DE');
const germanCities = ['München', 'Berlin', 'Köln', 'Hamburg'];
const sortedCities = germanCities.sort(collator.compare);
const germanNames = ['Müller', 'Schmidt', 'Schröder', 'Fischer'];
const sortedNames = germanNames.sort(collator.compare);
console.log(sortedCities);
// Output: ['Berlin', 'Hamburg', 'Köln', 'München']
console.log(sortedNames);
// Output: ['Fischer', 'Müller', 'Schmidt', 'Schröder']
The same collator handles both arrays without needing to create a new instance.
When to use Intl.Collator
Use Intl.Collator when sorting arrays with hundreds or thousands of items. The performance benefit increases with array size because the comparison function is called many times during sorting.
const collator = new Intl.Collator('en-US');
const products = [/* array with 10,000 product names */];
const sorted = products.sort(collator.compare);
For arrays larger than a few hundred items, the collator can be several times faster than localeCompare().
Also use Intl.Collator when you need to sort multiple arrays with the same locale and options. Creating the collator once and reusing it eliminates repeated locale data lookups.
const collator = new Intl.Collator('fr-FR');
const firstNames = ['Amélie', 'Bernard', 'Émilie', 'François'];
const lastNames = ['Dubois', 'Martin', 'Lefèvre', 'Bernard'];
const sortedFirstNames = firstNames.sort(collator.compare);
const sortedLastNames = lastNames.sort(collator.compare);
This pattern works well when building table views or other interfaces that display multiple sorted lists.
How to specify the locale
Both localeCompare() and Intl.Collator accept a locale identifier as their first argument. This identifier uses the BCP 47 format, typically combining a language code and optional region code.
const names = ['Åsa', 'Anna', 'Ärla'];
// Swedish locale
const swedishSorted = names.sort((a, b) => a.localeCompare(b, 'sv-SE'));
console.log(swedishSorted);
// Output: ['Anna', 'Åsa', 'Ärla']
// German locale
const germanSorted = names.sort((a, b) => a.localeCompare(b, 'de-DE'));
console.log(germanSorted);
// Output: ['Anna', 'Ärla', 'Åsa']
The locale determines which collation rules apply. Swedish and German have different rules for å and ä, which produces different sort orders.
You can omit the locale to use the user's default locale from the browser.
const collator = new Intl.Collator();
const names = ['Åsa', 'Anna', 'Ärla'];
const sorted = names.sort(collator.compare);
This approach respects the user's language preferences without hardcoding a specific locale. The sorted order will match what the user expects based on their browser settings.
You can also pass an array of locales to provide fallback options.
const collator = new Intl.Collator(['sv-SE', 'sv', 'en-US']);
The API uses the first supported locale from the array. If Swedish from Sweden is unavailable, it tries generic Swedish, then falls back to US English.
How to control case sensitivity
The sensitivity option determines how the comparison treats differences in case and accents. It accepts four values: base, accent, case, and variant.
The base sensitivity ignores both case and accents, comparing only the base characters.
const collator = new Intl.Collator('en-US', { sensitivity: 'base' });
console.log(collator.compare('a', 'A'));
// Output: 0 (equal)
console.log(collator.compare('a', 'á'));
// Output: 0 (equal)
console.log(collator.compare('a', 'b'));
// Output: -1 (different base characters)
This mode treats a, A, and á as identical because they share the same base character.
The accent sensitivity considers accents but ignores case.
const collator = new Intl.Collator('en-US', { sensitivity: 'accent' });
console.log(collator.compare('a', 'A'));
// Output: 0 (equal, case ignored)
console.log(collator.compare('a', 'á'));
// Output: -1 (different, accent matters)
The case sensitivity considers case but ignores accents.
const collator = new Intl.Collator('en-US', { sensitivity: 'case' });
console.log(collator.compare('a', 'A'));
// Output: -1 (different, case matters)
console.log(collator.compare('a', 'á'));
// Output: 0 (equal, accent ignored)
The variant sensitivity (the default) considers all differences.
const collator = new Intl.Collator('en-US', { sensitivity: 'variant' });
console.log(collator.compare('a', 'A'));
// Output: -1 (different)
console.log(collator.compare('a', 'á'));
// Output: -1 (different)
This mode provides the strictest comparison, treating any difference as significant.
How to sort strings with embedded numbers
The numeric option enables numeric sorting for strings that contain numbers. When enabled, the comparison treats sequences of digits as numeric values instead of comparing them character by character.
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
// Default sorting (wrong order)
const defaultSorted = [...files].sort();
console.log(defaultSorted);
// Output: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
// Numeric sorting (correct order)
const collator = new Intl.Collator('en-US', { numeric: true });
const numericSorted = files.sort(collator.compare);
console.log(numericSorted);
// Output: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']
Without numeric sorting, the strings sort character by character. The string 10 comes before 2 because the first character 1 has a lower code point than 2.
With numeric sorting enabled, the collator recognizes 10 as the number ten and 2 as the number two. This produces the expected sort order where 2 comes before 10.
This option is useful for sorting file names, version numbers, or any strings that mix text and numbers.
const versions = ['v1.10', 'v1.2', 'v1.20', 'v1.3'];
const collator = new Intl.Collator('en-US', { numeric: true });
const sorted = versions.sort(collator.compare);
console.log(sorted);
// Output: ['v1.2', 'v1.3', 'v1.10', 'v1.20']
How to control which case comes first
The caseFirst option determines whether uppercase or lowercase letters sort first when comparing strings that differ only in case. It accepts three values: upper, lower, or false.
const words = ['apple', 'Apple', 'APPLE'];
// Uppercase first
const upperFirst = new Intl.Collator('en-US', { caseFirst: 'upper' });
const upperSorted = [...words].sort(upperFirst.compare);
console.log(upperSorted);
// Output: ['APPLE', 'Apple', 'apple']
// Lowercase first
const lowerFirst = new Intl.Collator('en-US', { caseFirst: 'lower' });
const lowerSorted = [...words].sort(lowerFirst.compare);
console.log(lowerSorted);
// Output: ['apple', 'Apple', 'APPLE']
// Default (locale-dependent)
const defaultCase = new Intl.Collator('en-US', { caseFirst: 'false' });
const defaultSorted = [...words].sort(defaultCase.compare);
console.log(defaultSorted);
// Output depends on locale
The value false uses the locale's default case ordering. Most locales treat strings that differ only in case as equal when using default sensitivity settings.
This option only has an effect when the sensitivity option allows case differences to matter.
How to ignore punctuation in sorting
The ignorePunctuation option tells the collator to skip punctuation marks when comparing strings. This can be useful when sorting titles or phrases that may or may not include punctuation.
const titles = [
'The Old Man',
'The Old-Man',
'The Oldman',
];
// Default (punctuation matters)
const defaultCollator = new Intl.Collator('en-US');
const defaultSorted = [...titles].sort(defaultCollator.compare);
console.log(defaultSorted);
// Output: ['The Old Man', 'The Old-Man', 'The Oldman']
// Ignore punctuation
const noPunctCollator = new Intl.Collator('en-US', { ignorePunctuation: true });
const noPunctSorted = [...titles].sort(noPunctCollator.compare);
console.log(noPunctSorted);
// Output: ['The Old Man', 'The Old-Man', 'The Oldman']
When punctuation is ignored, the comparison treats the hyphen in "Old-Man" as if it does not exist, making the strings compare as if they were all "TheOldMan".
Sorting user names from different countries
When sorting names from users around the world, use the user's preferred locale to respect their linguistic expectations.
const userLocale = navigator.language;
const collator = new Intl.Collator(userLocale);
const users = [
{ name: 'Müller', country: 'Germany' },
{ name: 'Martin', country: 'France' },
{ name: 'Andersson', country: 'Sweden' },
{ name: 'García', country: 'Spain' },
];
const sorted = users.sort((a, b) => collator.compare(a.name, b.name));
sorted.forEach(user => {
console.log(`${user.name} (${user.country})`);
});
This code detects the user's locale from the browser and sorts the names accordingly. A German user sees the list sorted by German rules, while a Swedish user sees it sorted by Swedish rules.
Sorting with locale switching
When your application allows users to switch languages, update the collator when the locale changes.
let currentLocale = 'en-US';
let collator = new Intl.Collator(currentLocale);
function setLocale(newLocale) {
currentLocale = newLocale;
collator = new Intl.Collator(currentLocale);
}
function sortItems(items) {
return items.sort(collator.compare);
}
// User switches to Swedish
setLocale('sv-SE');
const names = ['Åsa', 'Anna', 'Örjan'];
console.log(sortItems(names));
// Output: ['Anna', 'Åsa', 'Örjan']
// User switches to German
setLocale('de-DE');
const germanNames = ['Über', 'Uhr', 'Udo'];
console.log(sortItems(germanNames));
// Output: ['Udo', 'Uhr', 'Über']
This pattern ensures sorted lists update to match the user's selected language.
Choosing between localeCompare and Intl.Collator
Use localeCompare() when you need a quick one-off comparison or are sorting a small array with fewer than 100 items. The simpler syntax is easier to read and the performance difference is negligible for small datasets.
const items = ['banana', 'Apple', 'cherry'];
const sorted = items.sort((a, b) => a.localeCompare(b, 'en-US'));
Use Intl.Collator when sorting large arrays, performing many comparisons, or sorting multiple arrays with the same locale and options. Creating the collator once and reusing it provides better performance.
const collator = new Intl.Collator('en-US', { sensitivity: 'base', numeric: true });
const products = [/* large array */];
const sorted = products.sort(collator.compare);
Both approaches produce the same results. The choice depends on your performance requirements and code organization preferences.