Lesson 7 of 9 · 18m
Sorting: More Than `sort()`
When the built-in sort isn't enough — counting sort, radix sort, topological sort, and the trade-offs behind each.
sorted() is O(n log n) and correct for nearly everything. The interesting question is what happens when O(n log n) is too slow — or when "sort" doesn't mean what you think it means.
The comparison sorts
- Timsort (Python, Java) — mergesort + insertion sort hybrid; exploits existing runs of sorted data. Best general-purpose sort.
- Quicksort — in-place on average; O(n²) worst case (bad pivot), which is why real implementations (introsort) fall back to heapsort.
- Mergesort — stable, O(n log n) guaranteed, O(n) extra memory. Good for linked lists, bad for cache locality.
Non-comparison sorts
- Counting sort — O(n + k) for small integer ranges (k = range). Counts frequencies, doesn't compare — beats comparison sorts when k is small.
- Radix sort — sorts integers digit-by-digit using counting sort — O(nk) where k is digits. Beats comparison sorts on fixed-size ints.
- Bucket sort — distribute into buckets, sort each, concatenate — good for uniform distributions.
Sorting isn't always the answer
- Top-K — a heap (O(n log k)) beats sorting the whole list (O(n log n)).
- Order statistics — quickselect finds the k-th smallest in O(n) average without full sorting.
- Is it already sorted? — a check (
sorted(x) == x) costs O(n) and catches the common "I don't need to sort this" case.
Field notes
- Stability matters: a stable sort preserves the order of equal elements —
sort(by=name)thensort(by=age)keeps names in order within each age group. - The real cost of sorting isn't the sort — it's the memory access pattern; a linked-list sort is slower than an array sort even at the same complexity.