Heap sort in 4 minutes
Channel: Michael Sambol
Watch on YouTube →Algorithm • hard
Heap sort is two algorithms in one: build a max-heap from the array, then repeatedly extract the max and place it at the end of the unsorted region.
It guarantees **O(n log n) in the worst case** with **O(1) extra memory**. It's the only mainstream comparison sort with both properties.
It's not stable and has poor cache locality compared to merge sort or quicksort, which is why most language standard libraries don't choose it as the default.
Typical Complexity Baseline
| Metric | Value |
|---|---|
| Time | O(n log n) (best/avg/worst) |
| Space | O(1) |
See It
A small animated SVG that illustrates the core idea the moving parts you need to keep in your head when you read the rest of the guide.
Build a max-heap, then move the root to the end and re-heapify the smaller heap.
Watch First
A quick visual walkthrough before the detailed sections pick the format that helps you learn fastest.
Channel: Michael Sambol
Watch on YouTube →Mechanics
The building blocks and terminology you need before everything else clicks. Skim or deep-read.
What it is Every parent is ≥ both children. The largest value is always at index 0.
Why it matters Max-heap property is a required building block for understanding how Heap Sort stays correct and performant on large inputs.
What it is Heap lives in the array: parent of i is (i−1)/2, children are 2i+1 and 2i+2.
Why it matters Array representation is a required building block for understanding how Heap Sort stays correct and performant on large inputs.
What it is Bottom-up sift-down from the last non-leaf to index 0, O(n) total.
Why it matters Heapify (build) is a required building block for understanding how Heap Sort stays correct and performant on large inputs.
What it is Push a value down to its correct level by swapping with the larger child.
Why it matters Sift down is a required building block for understanding how Heap Sort stays correct and performant on large inputs.
What it is Swap root with last unsorted element, shrink heap, sift the new root down.
Why it matters Extract max is a required building block for understanding how Heap Sort stays correct and performant on large inputs.
What it is Every level filled except possibly the last, which fills left-to-right. This is what lets us store the heap in a flat array.
Why it matters Every level filled except possibly the last, which fills left-to-right. This is what lets us store the heap in a flat array. In Heap Sort, this definition helps you reason about correctness and complexity when inputs scale.
What it is Convert an arbitrary array into a heap in O(n) time using bottom-up sift-down.
Why it matters Convert an arbitrary array into a heap in O(n) time using bottom-up sift-down. In Heap Sort, this definition helps you reason about correctness and complexity when inputs scale.
What it is Restore heap property at a node by swapping with the larger child until the property holds.
Why it matters Restore heap property at a node by swapping with the larger child until the property holds. In Heap Sort, this definition helps you reason about correctness and complexity when inputs scale.
What it is Uses O(1) extra memory the heap and the sorted output share the array.
Why it matters Uses O(1) extra memory the heap and the sorted output share the array. In Heap Sort, this definition helps you reason about correctness and complexity when inputs scale.
What it is Sift-down can swap a value past an equal value, breaking original input order.
Why it matters Sift-down can swap a value past an equal value, breaking original input order. In Heap Sort, this definition helps you reason about correctness and complexity when inputs scale.
Walkthrough
Every frame of the concept diagram, laid out top-to-bottom so you can scroll through the algorithm at your own pace.
Max-heap of 8 values. Each parent ≥ its children. Root = 9 (the max).
Extract 9: swap root with last leaf (2).
Shrink heap 9 is locked. Sift down 2: max of children (7, 8) is 8.
2 < 8 → swap. 2 moves to index 2.
Continue sift: max of children (3, 4) is 4.
2 < 4 → swap. No more children → sift complete.
Extract 8: swap root with last (2).
Sift 2: max of (7, 4) is 7.
Swap 2 ↔ 7.
Continue: max of (5, 6) is 6.
Swap 2 ↔ 6. No more children → done.
Extract 7: swap root with last (3).
Sift 3: max of (6, 4) is 6.
Swap 3 ↔ 6.
Continue: max of (5, 2) is 5.
Swap 3 ↔ 5. Done.
Extract 6: swap root with last (2).
Sift 2: max of (5, 4) is 5.
Swap 2 ↔ 5.
Continue: only one child (3). 2 < 3 → swap.
Done.
Extract 5: swap root with last (2).
Sift 2: max of (3, 4) is 4.
Swap 2 ↔ 4. No more children → done.
Extract 4: swap root with last (2).
Sift 2: child is 3. 2 < 3 → swap.
Done.
Extract 3: swap root with last (2).
Heap is just [2] already valid.
Last element drops to the tail. Done n log n total work.
Tradeoffs
When this technique wins, when it loses, and what to reach for instead.
Choose this when Choose heap sort when you cannot tolerate quicksort's O(n²) worst case (adversarial input, hard real-time deadlines).
Tradeoff Quick sort is typically 2–3× faster in practice due to cache locality and lower constant factors.
Choose this when Choose heap sort when you need O(1) extra space merge sort needs O(n).
Tradeoff Merge sort is stable and friendlier to the cache; heap sort jumps around the array.
Choose this when Use heap sort when you need a fully sorted result; use a priority queue when you only need partial extraction (top-k, streaming).
Tradeoff PQ is the right structure for incremental insert/extract; heap sort is for one-shot sorting.
In Production
Where this shows up at real companies and what the on-call engineer learned the hard way.
The kernel uses heap sort (sort.c) for a worst-case-bounded in-place sort because dynamic allocation is risky in kernel context and quicksort's O(n²) is unacceptable.
Takeaway When you can't allocate and can't gamble on average-case, heap sort is the safe default.
Frame schedulers that must hit a 16.6ms deadline at 60fps prefer heap sort or heap-based PQs over quicksort because the latency upper bound is what determines whether a frame drops.
Takeaway Predictable worst-case beats faster average-case under hard deadlines.
Systems that find top-K trending items, hottest queries, or k-largest values stream data through a min-heap of size K a direct application of heap sort's mechanics.
Takeaway The heap data structure shows up far more often than heap sort itself.
Code
A clean reference implementation in TypeScript, Python, and Go. Switch languages with the toggle.
First build a max-heap in O(n) via bottom-up sift-down. Then repeatedly swap root to end and sift down the new root.
Complexity: Time O(n log n), Space O(1), Unstable
In-place heap sort with bottom-up heapify
function heapSort(values: number[]): number[] {
const sortedValues = [...values]
const arrayLength = sortedValues.length
function siftDown(rootIndex: number, heapEnd: number): void {
let currentIndex = rootIndex
while (true) {
const leftChildIndex = 2 * currentIndex + 1
const rightChildIndex = 2 * currentIndex + 2
let largestIndex = currentIndex
if (leftChildIndex < heapEnd && sortedValues[leftChildIndex] > sortedValues[largestIndex]) largestIndex = leftChildIndex
if (rightChildIndex < heapEnd && sortedValues[rightChildIndex] > sortedValues[largestIndex]) largestIndex = rightChildIndex
if (largestIndex === currentIndex) return
;[sortedValues[currentIndex], sortedValues[largestIndex]] = [sortedValues[largestIndex], sortedValues[currentIndex]]
currentIndex = largestIndex
}
}
// Build heap (O(n) bottom-up)
for (let heapifyRoot = Math.floor(arrayLength / 2) - 1; heapifyRoot >= 0; heapifyRoot -= 1) {
siftDown(heapifyRoot, arrayLength)
}
// Extract max repeatedly
for (let heapEnd = arrayLength - 1; heapEnd > 0; heapEnd -= 1) {
;[sortedValues[0], sortedValues[heapEnd]] = [sortedValues[heapEnd], sortedValues[0]]
siftDown(0, heapEnd)
}
return sortedValues
}
Watch Out
The bugs and edge cases that bite engineers most often. Skim before you ship.
O(n log n)) instead of bottom-up (O(n)) most people get this wrong on the first pass.Pro Tips
Small habits that pay off every time you reach for this pattern.
Pattern Recognition
Concrete signals that tell you this is the right pattern both at work and on LeetCode.
O(n log n) guarantee without quicksort's O(n²) corner cases.heappush/heappop is essentially heap sort's inner mechanic.Practice
A curated easy-to-hard problem ladder. Each one reinforces a specific aspect of the pattern.
| # | Problem | Difficulty | Typical Complexity |
|---|---|---|---|
| 1 | Kth Largest Element in an Array | Medium | O(n log k) heap, O(n) QuickSelect |
| 2 | Top K Frequent Elements | Medium | O(n log k) |
| 3 | K Closest Points to Origin | Medium | O(n log k) |
| 4 | Sort an Array | Medium | O(n log n) with heap sort |
| 5 | Find Median from Data Stream | Hard | O(log n) per insert |
| 6 | Merge k Sorted Lists | Hard | O(n log k) |
| 7 | Sliding Window Maximum | Hard | O(n) deque, O(n log k) heap |