Sparround

Heap and Priority Queue

A heap is a complete binary tree that maintains one invariant:

  • Min-heap: every parent is less than or equal to its children. The smallest element is always at the root.
  • Max-heap: every parent is greater than or equal to its children. The largest element is at the root.

Note: this is NOT a BST. In a heap there is no ordering between siblings — only the parent–child relationship is constrained. That is why asking "does value X exist?" costs O(n) in a heap; a heap answers one question fast: "which is the smallest (or largest)?"

Being a complete tree matters: every level is full and only the last level is filled left to right. Exactly that property lets a heap live inside an array, with no pointers at all.

Index arithmetic in the array — this is the most elegant part of a heap. In a 0-based array:

  • parent(i) = (i - 1) / 2 (integer division)
  • left(i) = 2 * i + 1
  • right(i) = 2 * i + 2

The tree structure falls entirely out of arithmetic. That buys two big wins: no memory spent on pointers, and excellent cache locality because all data sits contiguously.

The two core operations:

Sift up (heapify up) — a new element is appended to the end of the array, then compared with its parent and swapped if needed, continuing upward until the invariant is restored. At most the tree's height in steps: O(log n).

Sift down (heapify down) — when the root is removed, the last element is moved to the root, then compared with the smaller of its two children (for a min-heap) and pushed down as needed. Again O(log n).

These two operations are the whole heap: insert uses sift up, extractMin uses sift down.

OperationComplexityWhy
peek (min/max)O(1)Always at `array[0]`
insertO(log n)Append + sift up, at most the height
extractMin / extractMaxO(log n)Take the root, move the last up, sift down
build-heap (from n elements)O(n) — not O(n log n)Most nodes sit low and move little; the sum collapses to O(n)
search (arbitrary value)O(n)No order between siblings — you cannot pick a branch
sorted iterationO(n log n)You must extract repeatedly (heapsort)

Why is build-heap O(n)? This is the fact that surprises people most in interviews.

The naive reasoning: n elements, each sifting O(log n) — therefore O(n log n). But the right way is to count elements level by level. Heapifying bottom-up:

  • Half the nodes are leaves and never move (0 steps).
  • A quarter move at most 1 step.
  • An eighth move at most 2 steps — and so on.

The sum n/2·0 + n/4·1 + n/8·2 + ... converges to 2n, i.e. O(n). In plain words: the vast majority of nodes sit near the bottom and do very little work.

The practical consequence: to turn an existing array into a heap, do a bottom-up build-heap (O(n)) rather than calling insert n times (O(n log n)).

Classic uses:

  • Top-K — find the k largest of n elements. Sorting everything is O(n log n); keeping a min-heap of size k is O(n log k). When k is small (say 10) that is an enormous difference.
  • Merging k sorted lists — keep each list's current element in the heap, extract the smallest and pull the next from that list: O(N log k).
  • Scheduling — the earliest deadline or highest priority is always at the root. Task queues, event loops, Dijkstra's algorithm.
  • Running median — two heaps (one max-heap, one min-heap) keep a streaming median updatable in O(log n).

Interview tip. The most frequent question here is top-K: "How do you find the 10 largest of a million numbers?"

Weak answer: "Sort and take the first 10" — O(n log n), and it requires holding all the data in memory.

Strong answer: "I keep a min-heap of size k = 10. For each new element: if the heap holds fewer than 10, push it; otherwise compare it with the root (the smallest in the heap) and, if it is larger, pop the root and push the new one. That is O(n log k) time and O(k) memory."

Two details impress interviewers most here: (1) why a min-heap rather than a max-heap — because the candidate you must discard is the smallest, and you need to see it in O(1); (2) memory is O(k), so you never hold the million numbers at once — decisive for streaming data.

The second classic is that build-heap is O(n). Knowing it and being able to justify it in one sentence ("half the nodes are leaves and never move") is a strong signal.

The language detail helps too: JS has NO built-in heap — you write it yourself; Kotlin/Java have java.util.PriorityQueue; Dart has HeapPriorityQueue from package:collection. Knowing this shows practical experience.

📚 Sources and documentation