Skip to Content
Grab N GoSorting

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) // descending
Compare 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 unchanged
Sort 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 - a

Beginner 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 compare
Sort 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 next
Stable 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 1
Sort 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 → low
Sort 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 biased

Advanced 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 context
Topological 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 dependents
Cached 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 cached
Insertion 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