To sort an array in TypeScript, call sort() with a compare function: numbers.sort((a, b) => a - b) for numbers and names.sort((a, b) => a.localeCompare(b)) for text. Swap a and b for descending order. Without a compare function sort() turns every element into a string first, which is why [100, 25, 9, 1000, 3] comes back as [100, 1000, 25, 3, 9]. It also sorts the array in place, so use toSorted() or a copy when the original matters. Everything below is compiled and run, including the sorting rules that are easy to get wrong.
Every example was compiled and run with TypeScript 5.2.2 and Node.js v22.22.2 in the Windows Command Prompt, with tsc --target es2022 --lib es2023,dom --strict file.ts and then node file.js. The output is the real output. Reference: Array.prototype.sort() on MDN.
Sort arrays in TypeScript
The compare function receives two elements and returns a negative number if a comes first, a positive number if b does, and 0 when they tie. That is all there is to it:
const prices: number[] = [100, 25, 9, 1000, 3];
console.log([...prices].sort()); // default: compared as TEXT
console.log([...prices].sort((a, b) => a - b)); // ascending
console.log([...prices].sort((a, b) => b - a)); // descending
const cities: string[] = ["Seattle", "austin", "Denver"];
console.log([...cities].sort()); // uppercase first
console.log([...cities].sort((a, b) => a.localeCompare(b))); // dictionary order
Output:
[ 100, 1000, 25, 3, 9 ]
[ 3, 9, 25, 100, 1000 ]
[ 1000, 100, 25, 9, 3 ]
[ 'Denver', 'Seattle', 'austin' ]
[ 'austin', 'Denver', 'Seattle' ]

1000 lands between 100 and 25.| What you are sorting | Compare function |
|---|---|
| Numbers, ascending | (a, b) => a - b |
| Numbers, descending | (a, b) => b - a |
| Text | (a, b) => a.localeCompare(b) |
| Text, ignoring case | (a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }) |
| Text with numbers in it | (a, b) => a.localeCompare(b, undefined, { numeric: true }) |
| Dates | (a, b) => a.date.getTime() - b.date.getTime() |
| Two keys at once | (a, b) => a.team.localeCompare(b.team) || a.name.localeCompare(b.name) |
Sort numbers in TypeScript
Subtraction gives the comparator exactly the negative, zero or positive number it expects, and it handles negatives and decimals without any extra work:
const temperatures: number[] = [68, -4, 102, 0, 55.5];
console.log([...temperatures].sort((a, b) => a - b));
console.log([...temperatures].sort((a, b) => b - a));
const scores: (number | undefined)[] = [88, undefined, 95, 72];
console.log([...scores].sort((a, b) => (a ?? 0) - (b ?? 0))); // undefined ignores the comparator
Output:
[ -4, 0, 55.5, 68, 102 ]
[ 102, 68, 55.5, 0, -4 ]
[ 72, 88, 95, undefined ]
Notice the last line: undefined is never passed to your compare function. The specification moves every undefined to the end of the array before sorting starts, so it always finishes last, whatever you write. Empty slots in a sparse array behave the same way.
The compare function has to return a number
Writing a > b is the most common sorting bug in JavaScript, because a boolean is silently treated as 0 or 1 and the result is only half sorted. In TypeScript it never compiles:
const prices: number[] = [100, 25, 9];
prices.sort((a, b) => a > b); // a comparator must return a number
tsc output:
sort_boolean.ts(3,13): error TS2345: Argument of type '(a: number, b: number) => boolean' is not assignable to parameter of type '(a: number, b: number) => number'.
Type 'boolean' is not assignable to type 'number'.

boolean is rejected at compile time.Sort a string array alphabetically
The default sort compares UTF-16 code units, so every capital letter sorts before every lowercase one, and accented letters land after z. localeCompare() sorts the way a person would, and its options handle case and embedded numbers. There is a longer treatment in our guide to sorting an array alphabetically in TypeScript:
const names: string[] = ["Emma", "olivia", "Ashley", "bob"];
console.log([...names].sort()); // UTF-16 code units
console.log([...names].sort((a, b) => a.localeCompare(b))); // dictionary order
// how the two compare "a" and "A": -1 means "a" first, 0 means they are equal
console.log("default:", "a".localeCompare("A"),
"| base:", "a".localeCompare("A", undefined, { sensitivity: "base" }));
const files: string[] = ["item10", "item9", "Item2"];
console.log([...files].sort()); // "item10" before "item9"
console.log([...files].sort((a, b) => a.localeCompare(b, undefined, { numeric: true })));
Output:
[ 'Ashley', 'Emma', 'bob', 'olivia' ]
[ 'Ashley', 'bob', 'Emma', 'olivia' ]
default: -1 | base: 0
[ 'Item2', 'item10', 'item9' ]
[ 'Item2', 'item9', 'item10' ]

"item10" before "item9" is the default; numeric: true fixes it.The third line shows what the sensitivity option does: by default "a" sorts before "A", while sensitivity: "base" calls them equal, which is what you want when case should never decide the order. For large arrays, build one Intl.Collator and pass its compare method instead of calling localeCompare() on every comparison; there are numbers for that further down.
sort() changes your array: use toSorted() or a copy
sort() returns the very same array it sorted, so anything else holding that array sees the new order. In React state, or anywhere a value is shared, sort a copy. toSorted() is the ES2023 way; it needs "lib": ["es2023", "dom"] and Node.js 20 or later:
const original: string[] = ["Seattle", "Austin", "Denver"];
const sorted = original.sort(); // same array, not a copy
console.log(sorted === original, original);
const untouched: string[] = ["Seattle", "Austin", "Denver"];
console.log([...untouched].sort(), untouched); // copy first
console.log(untouched.toSorted(), untouched); // ES2023: new array
Output:
true [ 'Austin', 'Denver', 'Seattle' ]
[ 'Austin', 'Denver', 'Seattle' ] [ 'Seattle', 'Austin', 'Denver' ]
[ 'Austin', 'Denver', 'Seattle' ] [ 'Seattle', 'Austin', 'Denver' ]
Because sorting mutates, TypeScript does not let you call sort() on a readonly array at all:
const regions: readonly string[] = ["West", "East", "Central"];
regions.sort(); // readonly arrays have no sort()
tsc output:
sort_readonly.ts(3,9): error TS2339: Property 'sort' does not exist on type 'readonly string[]'.
Sort an array of objects
Objects follow the same rules, applied to one property: subtract for numbers, localeCompare() for text, and getTime() for dates, since subtracting two Date values is a type error. For the full treatment, including a type-safe generic sortBy helper, see sorting an array of objects by property, and sorting arrays by date for date-heavy data:
interface Order {
id: string;
customer: string;
total: number;
placed: Date;
}
const orders: Order[] = [
{ id: "ORD-1001", customer: "Emma", total: 249.99, placed: new Date("2025-03-14") },
{ id: "ORD-1002", customer: "michael", total: 89.5, placed: new Date("2024-11-02") },
{ id: "ORD-1003", customer: "Olivia", total: 430, placed: new Date("2025-01-20") },
];
console.log([...orders].sort((a, b) => b.total - a.total).map(o => `${o.id} $${o.total}`));
console.log([...orders].sort((a, b) => a.customer.localeCompare(b.customer)).map(o => o.customer));
console.log([...orders].sort((a, b) => a.placed.getTime() - b.placed.getTime())
.map(o => o.placed.toISOString().slice(0, 10)));
Output:
[ 'ORD-1003 $430', 'ORD-1001 $249.99', 'ORD-1002 $89.5' ]
[ 'Emma', 'michael', 'Olivia' ]
[ '2024-11-02', '2025-01-20', '2025-03-14' ]
Sorting is stable, so you can sort twice
Since ES2019, elements that compare as equal keep their original order. That means sorting by one key and then by another gives you a proper two-level sort, which is handy when a user clicks column headings one after another. You can also do both in one comparator with ||, as most object-array work ends up doing:
interface Employee {
team: string;
name: string;
}
const staff: Employee[] = [
{ team: "Sales", name: "Ann" },
{ team: "Ops", name: "Zoe" },
{ team: "Sales", name: "Bob" },
{ team: "Ops", name: "Amy" },
];
const byName = [...staff].sort((a, b) => a.name.localeCompare(b.name));
console.log(byName.map(e => `${e.team}/${e.name}`).join(" "));
const byTeamThenName = [...byName].sort((a, b) => a.team.localeCompare(b.team));
console.log(byTeamThenName.map(e => `${e.team}/${e.name}`).join(" "));
// or do it in one comparator
const oneGo = [...staff].sort((a, b) => a.team.localeCompare(b.team) || a.name.localeCompare(b.name));
console.log(oneGo.map(e => `${e.team}/${e.name}`).join(" "));
Output:
Ops/Amy Sales/Ann Sales/Bob Ops/Zoe
Ops/Amy Ops/Zoe Sales/Ann Sales/Bob
Ops/Amy Ops/Zoe Sales/Ann Sales/Bob

Sort by a custom order, not by value
Sometimes the right order is not alphabetical or numeric at all. Map each value to a rank and subtract the ranks, which also keeps the compiler on your side: a Record<Priority, number> will not compile until every priority has a rank:
type Priority = "high" | "medium" | "low";
interface Ticket {
id: number;
priority: Priority;
}
const tickets: Ticket[] = [
{ id: 1, priority: "low" },
{ id: 2, priority: "high" },
{ id: 3, priority: "medium" },
{ id: 4, priority: "high" },
];
const order: Record<Priority, number> = { high: 0, medium: 1, low: 2 };
const queue = [...tickets].sort((a, b) => order[a.priority] - order[b.priority]);
console.log(queue.map(t => `#${t.id} ${t.priority}`).join(", "));
Output:
#2 high, #4 high, #3 medium, #1 low
Null values and numbers that arrive as text
API data is rarely tidy. Decide where null belongs and handle it before comparing, and convert numeric strings with Number(), or "1200.50" will sort before "89.00". The same applies when you sort by a date that arrives as a string:
interface Invoice {
id: string;
paid: string | null; // an ISO date, or null when unpaid
amount: string; // the API sends numbers as text
}
const invoices: Invoice[] = [
{ id: "INV-3", paid: null, amount: "1200.50" },
{ id: "INV-1", paid: "2025-02-10", amount: "89.00" },
{ id: "INV-2", paid: "2024-12-01", amount: "430.75" },
];
// nulls last, then oldest payment first
const byPaid = [...invoices].sort((a, b) =>
a.paid === null ? 1 : b.paid === null ? -1 : a.paid.localeCompare(b.paid)
);
console.log(byPaid.map(i => `${i.id} ${i.paid ?? "unpaid"}`).join(" | "));
// convert text to numbers before comparing, or "1200.50" sorts before "89.00"
console.log([...invoices].sort((a, b) => a.amount.localeCompare(b.amount)).map(i => i.amount));
console.log([...invoices].sort((a, b) => Number(a.amount) - Number(b.amount)).map(i => i.amount));
Output:
INV-2 2024-12-01 | INV-1 2025-02-10 | INV-3 unpaid
[ '1200.50', '430.75', '89.00' ]
[ '89.00', '430.75', '1200.50' ]
How fast is sorting, and does localeCompare cost much?
Sorting 100,000 email addresses three ways:
const emails: string[] = Array.from({ length: 100_000 }, (_, i) => `user${(i * 7919) % 100_000}@example.com`);
function time(label: string, compare?: (a: string, b: string) => number): void {
const copy = [...emails];
const start = performance.now();
copy.sort(compare);
console.log(label.padEnd(24), (performance.now() - start).toFixed(1).padStart(7), "ms", "first", copy[0]);
}
const collator = new Intl.Collator("en");
time("default sort()");
time("localeCompare", (a, b) => a.localeCompare(b));
time("Intl.Collator", collator.compare);

Intl.Collator beats localeCompare().The plain sort() only compares code units, which is why it wins on speed and loses on correctness for anything a human will read. Between the two correct options, one reused Intl.Collator is meaningfully faster than calling localeCompare() on every comparison, because the collator is built once instead of a hundred thousand times.
Other TypeScript array guides worth reading next:
- Loop through an array in TypeScript
- Add or append elements to an array
- Remove an item from an array
- Filter arrays in TypeScript
- Remove duplicates from an array
Frequently asked questions
How do I sort an array in TypeScript?
Pass a compare function to sort(): arr.sort((a, b) => a - b) for numbers, arr.sort((a, b) => a.localeCompare(b)) for strings.
Why does sort() put 100 before 25?
Without a compare function, sort() turns every element into a string and compares character by character, so "100" sorts before "25".
How do I sort in descending order?
Reverse the subtraction: (a, b) => b - a for numbers, or (a, b) => b.localeCompare(a) for text.
Does sort() change the original array in TypeScript?
Yes, it sorts in place and returns the same array. Use arr.toSorted(fn) or [...arr].sort(fn) to keep the original.
How do I sort strings ignoring upper and lower case?
Use a.localeCompare(b, undefined, { sensitivity: "base" }), which treats “a” and “A” as the same letter.
Why can’t I call sort() on a readonly array?
Sorting mutates the array, so readonly arrays do not have the method. Copy it first, or use toSorted().
Is TypeScript’s sort stable?
Yes. Since ES2019 equal elements keep their original order, so sorting by one key and then another produces a reliable two-level sort.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn