Sorting
Syntax
Array.sort()
const nums = [3, 1, 4, 1, 5]
// default — converts to strings, sorts lexicographically
nums.sort() // [1, 1, 3, 4, 5] — works for single digits
[10, 9, 80].sort() // [10, 80, 9] — WRONG! string order
// always pass a compare function for numbers
nums.sort((a, b) => a - b) // ascending
nums.sort((a, b) => b - a) // descendingCompare Function
// negative → a first
// positive → b first
// zero → keep order
const compare = (a: number, b: number) => a - b
// a=3, b=1 → 2 (positive) → b first → [1, 3]
// a=1, b=3 → -2 (negative) → a first → [1, 3]Sort Strings
const names = ['Charlie', 'alice', 'Bob']
// case-sensitive (uppercase first)
names.sort() // ['Bob', 'Charlie', 'alice']
// case-insensitive
names.sort((a, b) => a.localeCompare(b))
// ['alice', 'Bob', 'Charlie']toSorted (immutable)
const nums = [3, 1, 2]
// .sort() mutates the original array
nums.sort((a, b) => a - b) // nums is now [1, 2, 3]
// .toSorted() returns a new array
const sorted = [3, 1, 2].toSorted((a, b) => a - b)
// sorted = [1, 2, 3], original unchangedSort by Property
interface User { name: string; age: number }
const users: User[] = [
{ name: 'Bob', age: 30 },
{ name: 'Alice', age: 25 },
]
// by age ascending
users.toSorted((a, b) => a.age - b.age)
// by name alphabetical
users.toSorted((a, b) => a.name.localeCompare(b.name))Reverse
const nums = [1, 2, 3]
// .reverse() mutates the original
nums.reverse() // [3, 2, 1] — nums is now mutated
// .toReversed() returns a new array
const reversed = [1, 2, 3].toReversed() // [3, 2, 1]
// sort descending: just flip the compare (a, b) => b - aBeginner Patterns
Sort Dates
interface Post { title: string; date: string }
// newest first
posts.toSorted((a, b) =>
new Date(b.date).getTime() - new Date(a.date).getTime()
)
// oldest first — flip a and b
posts.toSorted((a, b) =>
new Date(a.date).getTime() - new Date(b.date).getTime()
)Sort Booleans
interface Task { text: string; done: boolean }
// incomplete first, done last
tasks.toSorted((a, b) => Number(a.done) - Number(b.done))
// done first
tasks.toSorted((a, b) => Number(b.done) - Number(a.done))
// false=0, true=1 — subtract to compareSort by Multiple Keys
// sort by role, then by name within each role
users.toSorted((a, b) => {
const role = a.role.localeCompare(b.role)
if (role !== 0) return role
return a.name.localeCompare(b.name)
})
// first key wins — ties fall through to the nextStable Sort
// JS sort is stable (ES2019+) — equal items keep order
const items = [
{ name: 'A', group: 1 },
{ name: 'B', group: 1 },
{ name: 'C', group: 2 },
]
items.toSorted((a, b) => a.group - b.group)
// A still comes before B within group 1Sort by Length
const words = ['banana', 'fig', 'cherry', 'date']
// shortest first
words.toSorted((a, b) => a.length - b.length)
// ['fig', 'date', 'banana', 'cherry']
// longest first
words.toSorted((a, b) => b.length - a.length)
// ['banana', 'cherry', 'date', 'fig']Sort Map Entries
const scores = new Map([['Bob', 90], ['Alice', 95], ['Eve', 85]])
// sort by value (score) descending
const ranked = [...scores.entries()]
.toSorted(([, a], [, b]) => b - a)
// [['Alice', 95], ['Bob', 90], ['Eve', 85]]Intermediate Patterns
Custom Sort Order
const priority = { high: 0, medium: 1, low: 2 } as const
interface Ticket { title: string; priority: keyof typeof priority }
tickets.toSorted((a, b) =>
priority[a.priority] - priority[b.priority]
)
// high → medium → lowSort Direction
type Dir = 'asc' | 'desc'
function sortBy<T>(items: T[], key: keyof T, dir: Dir): T[] {
return items.toSorted((a, b) => {
const av = a[key], bv = b[key]
const cmp = av < bv ? -1 : av > bv ? 1 : 0
return dir === 'asc' ? cmp : -cmp
})
}
sortBy(users, 'name', 'asc')
sortBy(users, 'age', 'desc')Sort Object Keys
const obj = { banana: 2, apple: 5, cherry: 1 }
// sort an object's entries by key
const sorted = Object.fromEntries(
Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))
)
// { apple: 5, banana: 2, cherry: 1 }
// sort by value
const byValue = Object.fromEntries(
Object.entries(obj).sort(([, a], [, b]) => a - b)
)
// { cherry: 1, banana: 2, apple: 5 }localeCompare Options
const items = ['é', 'e', 'f', 'ë']
// locale-aware sort with options
items.toSorted((a, b) =>
a.localeCompare(b, 'en', {
sensitivity: 'base', // ignore accents + case
numeric: true, // '2' before '10'
})
)
// numeric sort: ['item2', 'item10'] not ['item10', 'item2']Sort with Nulls
// push nulls to the end
function nullsLast(a: number | null, b: number | null) {
if (a === null) return 1
if (b === null) return -1
return a - b
}
[3, null, 1, null, 2].toSorted(nullsLast)
// [1, 2, 3, null, null]Shuffle (random sort)
// Fisher-Yates shuffle — fair random order
function shuffle<T>(arr: T[]): T[] {
const copy = [...arr]
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]]
}
return copy
}
// never use .sort(() => Math.random() - 0.5) — it's biasedAdvanced Patterns
Generic Sort Builder
type SortFn<T> = (a: T, b: T) => number
function by<T>(...fns: SortFn<T>[]): SortFn<T> {
return (a, b) => {
for (const fn of fns) {
const result = fn(a, b)
if (result !== 0) return result
}
return 0
}
}
// compose sort keys
users.toSorted(by(
(a, b) => a.role.localeCompare(b.role),
(a, b) => a.age - b.age,
(a, b) => a.name.localeCompare(b.name),
))Intl.Collator
// Intl.Collator is faster than localeCompare for large arrays
const collator = new Intl.Collator('en', {
sensitivity: 'base',
numeric: true,
})
const files = ['file10.txt', 'file2.txt', 'file1.txt']
files.toSorted(collator.compare)
// ['file1.txt', 'file2.txt', 'file10.txt']
// reuse the collator — avoids recreating locale contextTopological Sort
// sort items by dependency order (DAG)
function topoSort(graph: Map<string, string[]>): string[] {
const visited = new Set<string>()
const result: string[] = []
function visit(node: string) {
if (visited.has(node)) return
visited.add(node)
for (const dep of graph.get(node) ?? []) visit(dep)
result.push(node)
}
for (const node of graph.keys()) visit(node)
return result
}
// dependencies come before dependentsCached Sort
// cache sorted results to avoid re-sorting unchanged data
function createSortCache<T>() {
let prev: { items: T[]; key: keyof T; result: T[] } | null = null
return (items: T[], key: keyof T): T[] => {
if (prev && prev.items === items && prev.key === key) {
return prev.result
}
const result = items.toSorted((a, b) =>
a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0
)
prev = { items, key, result }
return result
}
}
const sortUsers = createSortCache<User>()
sortUsers(users, 'name') // sorts
sortUsers(users, 'name') // returns cachedInsertion Sort (small arrays)
// faster than .sort() for very small arrays (< ~20 items)
function insertionSort(arr: number[]): number[] {
const a = [...arr]
for (let i = 1; i < a.length; i++) {
const key = a[i]
let j = i - 1
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j]
j--
}
a[j + 1] = key
}
return a
}Last updated on