Overview
Sorting arranges elements in order. It is rarely the final answer to an interview problem, but it is very often the first step: sorting enables binary search, two pointers, greedy choices, merging intervals, and grouping duplicates.
You should know how the classic algorithms work, their complexity, and whether they are stable and in-place. In practice you call the built-in sort (Timsort for objects in Java and in V8's JavaScript; dual-pivot quicksort for Java primitives) and supply the right comparator.
Most people sort cards with insertion sort: pick up one card at a time and slide it left into its correct position among the cards already in hand. It is quick for a small hand and for a hand that is almost sorted already.
When to use it
- Order unlocks a faster technique: binary search, two pointers, or greedy.
- You need to group equal elements together or detect duplicates.
- Intervals, events, or meetings must be processed in time order.
- Values are small integers in a known range: counting sort gives O(n + k).
Problem patterns it solves
Recognize it when: pairs or triplets with a target sum on unsorted input.
- 15. 3Sum
- 18. 4Sum
- 16. 3Sum Closest
- 881. Boats to Save People
Recognize it when: assign, schedule, or match to maximize count or minimize cost.
- 455. Assign Cookies
- 826. Most Profit Assigning Work
- 435. Non-overlapping Intervals
- 1710. Maximum Units on a Truck
Recognize it when: merge, insert, or count overlapping ranges.
- 56. Merge Intervals
- 252. Meeting Rooms
- 452. Minimum Number of Arrows to Burst Balloons
Recognize it when: order by a rule, not by value: largest concatenated number, sort by frequency.
- 179. Largest Number
- 451. Sort Characters By Frequency
- 1636. Sort Array by Increasing Frequency
Recognize it when: values in a small range, or O(n) is required.
- 75. Sort Colors
- 347. Top K Frequent Elements
- 274. H-Index
- 1051. Height Checker
Recognize it when: count inversions or smaller-elements-after while sorting.
- 912. Sort an Array
- 315. Count of Smaller Numbers After Self
- 493. Reverse Pairs
Where it is used in real software
Databases sort query results, and use external merge sort when data does not fit in memory. B-tree indexes keep data sorted so ORDER BY can skip sorting.
Java's Arrays.sort uses dual-pivot quicksort for primitives and Timsort for objects; V8 (Chrome, Node.js) uses Timsort for Array.prototype.sort. Timsort is fast on partially sorted real-world data.
The shuffle phase of Hadoop and Spark sorts keys so all values for a key reach the same reducer.
Ranking pipelines sort candidates by score; often only the top K are needed, so a heap or partial sort is used instead of a full sort.
Key terms
- Stable sort
- Equal elements keep their original relative order. Needed when sorting by multiple keys in passes.
- In-place
- Uses O(1) or O(log n) extra memory.
- Comparison sort lower bound
- Any sort that only compares elements needs Omega(n log n) comparisons in the worst case.
- Comparator
- A function (a, b) that returns negative if a comes first, positive if b comes first, 0 if equal.
- Pivot
- The element quicksort partitions around.
The main algorithms
- 1Bubble sort: O(n^2), stable
Repeatedly swap adjacent out-of-order elements; the largest bubbles to the end each pass. Mostly educational.
- 2Selection sort: O(n^2), not stable
Find the minimum of the unsorted part and swap it into place. Makes only O(n) swaps.
- 3Insertion sort: O(n^2), stable, O(n) if nearly sorted
Insert each element into the sorted prefix by shifting larger elements right. Excellent for small or nearly sorted arrays.
- 4Merge sort: O(n log n), stable, O(n) space
Split in half, sort each half recursively, merge two sorted halves with two pointers.
- 5Quicksort: O(n log n) average, O(n^2) worst, in-place
Partition around a pivot so smaller elements go left and larger go right, then recurse on each side. Random pivots avoid the worst case.
- 6Heap sort: O(n log n), in-place, not stable
Build a max-heap, then repeatedly move the maximum to the end.
- 7Counting sort: O(n + k), stable
Count occurrences of each value in range k, then write them back in order. Not comparison-based.
STEP 1A single element is already sorted. The sorted prefix is [5].
Comparing sorting algorithms
n elements; k = range of values for counting sort
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Bubble | O(n) | O(n^2) | O(n^2) | O(1) | Yes |
| Selection | O(n^2) | O(n^2) | O(n^2) | O(1) | No |
| Insertion | O(n) | O(n^2) | O(n^2) | O(1) | Yes |
| Merge | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Quick | O(n log n) | O(n log n) | O(n^2) | O(log n) | No |
| Heap | O(n log n) | O(n log n) | O(n log n) | O(1) | No |
| Counting | O(n + k) | O(n + k) | O(n + k) | O(k) | Yes |
NOWAlgorithm: Bubble | Best: O(n) | Average: O(n^2) | Worst: O(n^2) | Space: O(1) | Stable: Yes
Merge sort guarantees O(n log n) and stability at the cost of memory. Quicksort is usually fastest in practice. Heap sort is in-place with guaranteed O(n log n). Counting sort breaks the n log n barrier when values are small integers.
Implementation
function insertionSort(arr) { for (let i = 1; i < arr.length; i++) { const current = arr[i]; let j = i - 1; while (j >= 0 && arr[j] > current) { arr[j + 1] = arr[j]; // shift right j--; } arr[j + 1] = current; } return arr;} function mergeSort(arr) { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); const merged = []; let i = 0, j = 0; while (i < left.length && j < right.length) { merged.push(left[i] <= right[j] ? left[i++] : right[j++]); // <= keeps it stable } return merged.concat(left.slice(i), right.slice(j));} function quickSort(arr, low = 0, high = arr.length - 1) { if (low >= high) return arr; const pivotIndex = low + Math.floor(Math.random() * (high - low + 1)); [arr[pivotIndex], arr[high]] = [arr[high], arr[pivotIndex]]; const pivot = arr[high]; let i = low; for (let j = low; j < high; j++) { if (arr[j] < pivot) { [arr[i], arr[j]] = [arr[j], arr[i]]; i++; } } [arr[i], arr[high]] = [arr[high], arr[i]]; quickSort(arr, low, i - 1); quickSort(arr, i + 1, high); return arr;} // Built-in: ALWAYS pass a comparator for numbers[10, 9, 1].sort(); // [1, 10, 9] (string comparison!)[10, 9, 1].sort((a, b) => a - b); // [1, 9, 10] // Multi-key: by age ascending, then nameconst people = [{ name: "Bo", age: 30 }, { name: "Al", age: 30 }, { name: "Cy", age: 25 }];people.sort((a, b) => a.age - b.age || a.name.localeCompare(b.name)); // Cy, Al, BoComplexity and performance
Use it unless the problem forbids it.
No comparison sort can do better in the worst case.
When k (value range) is small.
Insertion sort and Timsort exploit existing order.
Trade-offs
Merge sort is stable and predictable but needs O(n) extra memory. Quicksort is in-place and cache-friendly but has an O(n^2) worst case without random pivots.
If you only need the K largest elements, a heap of size K gives O(n log k), and quickselect gives O(n) average.
If the original positions matter, sort an array of indexes or [value, index] pairs.
Variants and related techniques
Partition like quicksort but recurse into only one side to find the kth element in O(n) average.
Three-way partition into less, equal, and greater than the pivot in one pass (Sort Colors).
Sort chunks that fit in memory, write them to disk, then k-way merge the sorted chunks.
Sort integers digit by digit with a stable counting sort: O(d x (n + base)).
Common mistakes
- Sorting numbers in JavaScript without a comparator.
Fix: Always use arr.sort((a, b) => a - b).
- Comparator a - b that overflows in Java.
Fix: Use Integer.compare(a, b) for values that may be large or negative.
- Inconsistent comparator.
Fix: It must be transitive and return 0 for equal elements, or sorting can throw or misbehave.
- Sorting when O(n) is required.
Fix: Check constraints; counting sort, bucket sort, or a hash map may be expected.
Interview questions
Why is comparison sorting at least n log n?
There are n! possible orderings. Each comparison has two outcomes, so a decision tree needs at least log2(n!) levels to distinguish them all, and log2(n!) is proportional to n log n.
What does stable mean and when does it matter?
Equal keys keep their original order. It matters when sorting by several keys in passes, such as sorting by name and then stably by department.
How do you avoid quicksort's worst case?
Choose a random pivot or median-of-three, and use three-way partitioning when there are many duplicates.
Which sort would you use for nearly sorted data?
Insertion sort or Timsort, which run close to O(n) when there are few inversions.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 912. Sort an Array | Medium | Implement merge sort and quicksort. |
| 75. Sort Colors | Medium | Dutch national flag partitioning. |
| 56. Merge Intervals | Medium | Sort by start, then merge. |
| 179. Largest Number | Medium | Custom comparator. |
| 215. Kth Largest Element in an Array | Medium | Quickselect vs heap. |
| 315. Count of Smaller Numbers After Self | Hard | Merge sort counting. |