Heap Sort

Sorting
In-place
avg O(n log n)

Heap sort uses a clever data structure - the max-heap - to sort in place with a guaranteed O(n log n). A max-heap is an array viewed as a tree where every parent is bigger than its children, so the biggest value is always at position 0. Heap sort builds that heap, then pulls the max out n times. No extra memory, no bad inputs - just slightly slower in practice than quick sort.

Watch it sort

Press play - or drag the timeline and step through it yourself.
Round 1Comparisons 0Swaps 0Step 1 / 167

18 values. Round 1: turn the whole array into a max-heap - every parent bigger than its children.

parent vs child swapping live heap sorted (final)

Complexity

Best
O(n log n)
Input already in order
Average
O(n log n)
Normal mixed input
Worst
O(n log n)
Worst possible input
Space
O(1)
Extra memory used
Stable: no - equal values keep their orderIn-place: yes - no copy of the array needed

A tournament bracket in an array

Think of a sports bracket: the champion sits at the top, and every player above another beat them. A max-heap is that bracket squeezed into an array - parent at position i, children at 2i+1 and 2i+2. Heap sort runs the tournament once (build the heap), then does something smart: it removes the champion, puts them at the end of the line, and lets the bracket repair itself in log n steps to find the next champion. Repeat n times and the line-up is sorted.

How it works, step by step

  1. View the array as a tree: the value at position i has children at positions 2i+1 and 2i+2. No pointers needed.

  2. Build a max-heap: starting from the last parent and going backwards, sift down every node - swap it with its bigger child until it is bigger than both.

  3. Now the biggest value of the whole array is at position 0.

  4. Swap it with the last value of the heap. The big value is now in its final place - shrink the heap by one so it is never touched again.

  5. The new root is probably in the wrong place - sift it down to restore the heap.

  6. Repeat swap-and-sift until the heap is empty. Each round costs log n, so the total is n log n.

The code, in JavaScript

The whole algorithm: heapify (build the heap), then extract the max n times. `siftDown` is the engine of both phases.

javascript
function heapSort(arr) {
  const a = [...arr];
  const n = a.length;

  // Phase 1: build a max-heap, from the last parent backwards.
  for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
    siftDown(a, i, n);
  }

  // Phase 2: pull the max out, again and again.
  for (let end = n - 1; end > 0; end--) {
    [a[0], a[end]] = [a[end], a[0]];  // max goes to its final slot
    siftDown(a, 0, end);              // repair the shrunken heap
  }

  return a;
}

function siftDown(a, i, size) {
  for (;;) {
    const left = 2 * i + 1;
    const right = 2 * i + 2;
    let biggest = i;

    if (left < size && a[left] > a[biggest]) biggest = left;
    if (right < size && a[right] > a[biggest]) biggest = right;
    if (biggest === i) return;        // parent already wins - done

    [a[i], a[biggest]] = [a[biggest], a[i]];
    i = biggest;                      // keep sinking
  }
}

heapSort([5, 1, 4, 2]);              // → [1, 2, 4, 5]

Dry run: sorting [5, 1, 4, 2]

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepRoundArrayWhat happened
11[5, 1, 4, 2]Compare parent 1 with left child 2.
21[5, 2, 4, 1]2 is bigger - swap it up. The parent sinks down.
31[5, 2, 4, 1]Compare parent 5 with left child 2.
41[5, 2, 4, 1]Compare 5 with right child 4.
52[1, 2, 4, 5]Swap the max (5) to the end - that slot is final. Heap shrinks to 3.
62[1, 2, 4, 5]Compare parent 1 with left child 2.
72[1, 2, 4, 5]Compare 2 with right child 4.
82[4, 2, 1, 5]4 is bigger - swap it up. The parent sinks down.
93[1, 2, 4, 5]Swap the max (4) to the end - that slot is final. Heap shrinks to 2.
103[1, 2, 4, 5]Compare parent 1 with left child 2.
113[2, 1, 4, 5]2 is bigger - swap it up. The parent sinks down.
124[1, 2, 4, 5]Swap the max (2) to the end - that slot is final. Heap shrinks to 1.

Good choice when…

  • You need a guaranteed O(n log n) and O(1) extra memory at the same time - the combination neither quick sort nor merge sort offers.
  • You only need the top k items: build a heap in O(n), pop k times - much cheaper than sorting everything.
  • Latency limits are hard (embedded, real-time): no worst-case blowup, no allocation, fully predictable.
  • You actually need a priority queue - the heap itself, not the sort, is the everyday tool.

Bad choice when…

  • Raw average speed is all that matters. Heap sort's memory access jumps around (i → 2i+1), which is unfriendly to CPU caches - quick sort usually beats it by 2-3×.
  • You need stability. Sifting moves values across long distances; equal items get reordered.
  • The data is nearly sorted - heap sort gains nothing from it, while insertion sort or TimSort run in almost O(n).

Common mistakes

  • Building the heap is O(n), not O(n log n) - the classic interview trap. Most nodes are near the bottom and sift only a step or two; the math sums to linear time.
  • Start heapify at `Math.floor(n / 2) - 1`, the last parent. Starting at n−1 wastes time on leaves; starting at 0 and going forward is simply wrong (children are not heaps yet).
  • The children of i are at 2i+1 and 2i+2 only when the heap starts at index 0. Textbook pseudocode often starts at 1 with children at 2i and 2i+1 - mixing the two conventions is a very common source of broken heaps.
  • After swapping the max to the end, sift down within the shrunken size (`end`), not the full array - otherwise the sorted tail gets pulled back into the heap.

Heap Sort vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
Heap Sortthis pageO(n log n)O(n log n)O(n log n)O(1)no
Quick SortO(n log n)O(n log n)O(n²)O(log n)no
Merge SortO(n log n)O(n log n)O(n log n)O(n)yes
Selection SortO(n²)O(n²)O(n²)O(1)no
Insertion SortO(n)O(n²)O(n²)O(1)yes