How to Sort an Array Alphabetically in TypeScript

To sort an array alphabetically in TypeScript, call sort() with localeCompare(): names.sort((a, b) => a.localeCompare(b)). Without it, sort() compares character codes, so capitals come before lowercase letters and accented names go last:

const sorted = cities.toSorted((a, b) => a.localeCompare(b));

I wanted the sorted lists here to be exactly what you’ll get, so each one was printed by TypeScript 7.0.2 under Node.js v22.22.2 in strict mode.

You’ll see why the default sort is wrong for text, localeCompare options for case, accents and numbers, Z-A order, arrays of objects by name, the readonly error and a faster collator.

Why does sort() put uppercase strings first?

Without a compare function, sort() orders strings by UTF-16 code units. Every capital letter comes before every lowercase one, accented letters go last, and digits compare one character at a time:

const cities: string[] = ["denver", "Austin", "Érie", "boston", "Chicago", "albany"];
console.log([...cities].sort());

const files: string[] = ["report10.csv", "report9.csv", "report1.csv", "report2.csv"];
console.log([...files].sort());

Output:

[ 'Austin', 'Chicago', 'albany', 'boston', 'denver', 'Érie' ]
[ 'report1.csv', 'report10.csv', 'report2.csv', 'report9.csv' ]
Command Prompt output of TypeScript default sort putting uppercase city names first and report10 before report9
The capitalized cities sort ahead of the lowercase ones, Érie lands last, and report10.csv comes before report9.csv.

That’s fine for IDs and codes, but not for anything a person reads. For sorting in general, including numbers, see sorting arrays in TypeScript.

Sort an array alphabetically with localeCompare() options

localeCompare() uses real language rules. Its options handle the cases that matter for strings: sensitivity for case and accents, and numeric for numbers inside text:

const cities: string[] = ["denver", "Austin", "Érie", "boston", "Chicago", "albany"];
console.log(cities.toSorted((a, b) => a.localeCompare(b)));

const names = ["emily", "Emily", "EMILY", "émily"];
console.log(names.toSorted((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })));
console.log("base equal?", "emily".localeCompare("Émily", "en", { sensitivity: "base" }) === 0);

const files: string[] = ["report10.csv", "report9.csv", "report1.csv", "report2.csv"];
console.log(files.toSorted((a, b) => a.localeCompare(b, undefined, { numeric: true })));

console.log("descending:", cities.toSorted((a, b) => b.localeCompare(a)));
console.log("original untouched:", cities);

Output:

[ 'albany', 'Austin', 'boston', 'Chicago', 'denver', 'Érie' ]
[ 'emily', 'Emily', 'EMILY', 'émily' ]
base equal? true
[ 'report1.csv', 'report2.csv', 'report9.csv', 'report10.csv' ]
descending: [ 'Érie', 'denver', 'Chicago', 'boston', 'Austin', 'albany' ]
original untouched: [ 'denver', 'Austin', 'Érie', 'boston', 'Chicago', 'albany' ]
Command Prompt output of TypeScript sorting strings with localeCompare, sensitivity base, numeric collation and descending order
The cities sort A to Z regardless of case, report1 to report10 come out in number order, and the original array is unchanged.

toSorted() (ES2023) returns a new array, so cities keeps its order; sort() changes the array in place. Flip a and b for descending. For numbers stored as numbers, see sorting a number array in descending order.

Sort an array of objects alphabetically by name

For objects, compare the property you want. Chain a second localeCompare with || to break ties, such as first name after last name:

interface Customer { first: string; last: string; city: string }

const customers: Customer[] = [
  { first: "Olivia", last: "Brown", city: "Tampa" },
  { first: "James", last: "wilson", city: "Denver" },
  { first: "Ava", last: "Brown", city: "Austin" },
  { first: "Liam", last: "Álvarez", city: "Reno" },
];

const byName = customers.toSorted(
  (a, b) => a.last.localeCompare(b.last, "en", { sensitivity: "base" }) || a.first.localeCompare(b.first),
);
byName.forEach((c) => console.log(`${c.last}, ${c.first} (${c.city})`));

const byCityDesc = customers.toSorted((a, b) => b.city.localeCompare(a.city));
console.log("cities Z-A:", byCityDesc.map((c) => c.city));

Output:

Álvarez, Liam (Reno)
Brown, Ava (Austin)
Brown, Olivia (Tampa)
wilson, James (Denver)
cities Z-A: [ 'Tampa', 'Reno', 'Denver', 'Austin' ]
Command Prompt output of TypeScript sorting customer objects alphabetically by last name then first name, and by city Z to A
Álvarez sorts first despite the accent, the two Browns are ordered by first name, the lowercase wilson still lands last, and the cities print Z to A.

|| only uses the second rule when the last names are equal. More property sorts, including numbers and dates, are in sorting an array of objects by property value.

Fix the sort error on a readonly string array

Constants are often typed readonly string[]. Calling sort() on one fails, because it would mutate the array:

const STATES: readonly string[] = ["Texas", "Ohio", "Iowa", "Utah"];

STATES.sort();

Output:

sort_readonly.ts(3,8): error TS2339: Property 'sort' does not exist on type 'readonly string[]'.
Command Prompt showing TypeScript error TS2339 property sort does not exist on type readonly string array
The compiler reports that sort doesn’t exist on readonly string[].

Use STATES.toSorted() or [...STATES].sort() instead; both leave the original alone. There’s more on this type in readonly arrays in TypeScript.

Sort large string arrays faster with Intl.Collator

localeCompare() with options sets up the comparison rules on every call. An Intl.Collator builds them once, which adds up on big arrays:

const firstNames = ["James", "Olivia", "Liam", "Emma", "Noah", "Ava", "Mason", "Sophia"];
const big: string[] = Array.from({ length: 50_000 }, (_, i) => firstNames[i % 8] + (i * 7919) % 100_000);

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

function time(label: string, fn: () => string[]): void {
  const start = performance.now();
  const out = fn();
  console.log(`${label.padEnd(22)} first: ${out[0].padEnd(8)} ${(performance.now() - start).toFixed(0)} ms`);
}

time("localeCompare(...opts)", () => big.toSorted((a, b) => a.localeCompare(b, "en", { numeric: true, sensitivity: "base" })));
time("Intl.Collator compare", () => big.toSorted(collator.compare));
time("default sort()", () => big.toSorted());

Output:

localeCompare(...opts) first: Ava19    3792 ms
Intl.Collator compare  first: Ava19    113 ms
default sort()         first: Ava10003 12 ms
Command Prompt timing TypeScript sorting 50,000 strings with localeCompare options, Intl.Collator and default sort
The collator finishes far sooner than localeCompare with options, and both agree on the first name; the default sort is quickest but puts Ava10003 first because it compares digit by digit.

Your timings will vary, but I create one collator per sort rule and reuse it. When the strings live inside objects, pass collator.compare(a.last, b.last) to the same compare function shown above.

For lists too long to show at once, sort first and then page through them with splitting an array in TypeScript.

Sort a string array by length or with missing values

A compare function can rank by anything. Here strings are sorted by length with alphabetical tie-breaks, then an array with blanks and undefined is sorted two ways:

const products = ["Mug", "Coffee beans", "Tea", "Filters", "French press"];

const byLength = products.toSorted((a, b) => a.length - b.length || a.localeCompare(b));
console.log(byLength);

const withBlanks: (string | undefined)[] = ["Reno", undefined, "Akron", "", "Mesa"];
console.log(withBlanks.toSorted((a, b) => (a ?? "").localeCompare(b ?? "")));
console.log([...withBlanks].sort());     // default sort moves undefined to the end

Output:

[ 'Mug', 'Tea', 'Filters', 'Coffee beans', 'French press' ]
[ '', 'Akron', 'Mesa', 'Reno', undefined ]
[ '', 'Akron', 'Mesa', 'Reno', undefined ]

|| falls through to the second rule only when the lengths are equal. Both blank-value sorts match: sort() moves undefined to the end without calling your function, but strict TypeScript still wants the ?? "" guard.

Empty strings, on the other hand, sort first. To strip empty strings before sorting, use removing empty strings from an array.

Alphabetical sorting options compared

GoalCompare function or method
Human A–Z order(a, b) => a.localeCompare(b)
Ignore case and accentslocaleCompare(b, "en", { sensitivity: "base" })
file2 before file10localeCompare(b, undefined, { numeric: true })
Z–A(a, b) => b.localeCompare(a)
Objects by name(a, b) => a.last.localeCompare(b.last)
Keep the originaltoSorted() or [...arr].sort()
Big arraysarr.sort(new Intl.Collator("en").compare)

Sorting is rarely the last step, so here’s where to go next:

Frequently asked questions

How do I sort an array alphabetically in TypeScript?

Use arr.sort((a, b) => a.localeCompare(b)), or toSorted() to keep the original array.

Why does sort() put “Zoe” before “adam”?

The default sort compares UTF-16 code units, and all uppercase letters come before lowercase ones.

How do I sort strings case-insensitively?

Pass { sensitivity: "base" } to localeCompare(), which treats a, A and á as equal.

How do I sort strings with numbers in natural order?

Use localeCompare(b, undefined, { numeric: true }) so “file2” sorts before “file10”.

How do I sort an array of objects alphabetically by name?

Compare the property: people.sort((a, b) => a.last.localeCompare(b.last)), adding a second localeCompare for ties.

Where is String.prototype.localeCompare documented?