Cost
| Operation | Time | Space | Why |
|---|---|---|---|
| Peek the minimum | O(1) | O(n) | It is the root, which is index 0. This is the operation the whole structure exists for. |
| Push / pop | O(log n) | — | One swap per level while sifting up or down, and the tree's height is log n because it is kept complete. |
| Build from an array (heapify) | O(n) | — | Sifting down from the last parent backwards. Most nodes are near the bottom and travel almost no distance, so the sum is linear — not O(n log n). |
| Top-k over n elements | O(n log k) | O(k) | A heap of size k, pushing each element and evicting the worst. Sorting everything would be O(n log n) and O(n) space. |
Reach for it when
- "K largest", "k closest", "k most frequent" — anything top-k, especially over a stream too big to sort.
- Scheduling by priority — the next task, the next expiring item, the next event in a simulation.
- Merging k sorted sequences, where the heap holds one candidate per sequence.
- Running median, with a max-heap for the low half and a min-heap for the high half.
- Dijkstra and A*, where the frontier must always yield the cheapest node next.
A weaker promise, bought cheaper
Sorting an array gives total order for O(n log n). Most problems do not need that — they need the best element, repeatedly. A heap provides exactly that and nothing more: the root is the minimum (or maximum), and siblings are unordered.
The structure is a complete binary tree, which means it can be stored in a plain array with no pointers at all:
index: 0 1 2 3 4 5 6
value: 2 5 8 9 6 12 10
parent(i) = (i - 1) // 2
left(i) = 2i + 1
right(i) = 2i + 2Completeness is what makes that arithmetic valid, and it is why every insert goes at the end and every delete takes from the end.
Two operations, mirrored
Push: append, then sift up while the parent is larger. Pop: take the root, move the last element into its place, then sift down into the smaller child while it is out of order. Both walk one path from root to leaf, so both are O(log n).
import heapq
heap = []
heapq.heappush(heap, 5)
smallest = heapq.heappop(heap)
heapq.heapify(existing_list) # O(n), in placeUse the library in an interview and say what it costs — nobody needs to watch you write sift-down unless they ask.
The top-k pattern
The single most common heap question, and the direction is counterintuitive:
def k_largest(nums, k):
heap = []
for value in nums:
heapq.heappush(heap, value)
if len(heap) > k:
heapq.heappop(heap) # drops the smallest survivor
return heap # the k largest, unorderedA min-heap for the k largest. The root is the weakest element still in the running, so it is exactly what should be evicted when a better one arrives. Cost: O(n log k) time, O(k) space — better than sorting on both counts whenever k is small, and it works on a stream that does not fit in memory.
Python's one-sided heap
heapq only does min-heaps. For a max-heap, negate on the way in and out, or push tuples:
heapq.heappush(heap, -value) # max-heap of numbers
heapq.heappush(heap, (-count, word)) # max by count, ties by wordThe tuple form is also how you attach a payload to a priority, which is what Dijkstra's frontier needs.
What to say in the interview
Say why a heap instead of sorting: "I only need the extreme repeatedly, so I can pay O(log n) per element instead of ordering everything." For top-k, name the direction and the reason — "min-heap of size k, so the root is the one to evict". Then give the cost as O(n log k) with O(k) space, and mention heapify being O(n) if you are building from an existing array.
Where candidates slip
- Using the wrong direction. For the k largest you need a min-heap of size k, so the weakest survivor is the one you evict — the instinct to use a max-heap is backwards.
- Treating the array as sorted. Beyond the root, a heap promises nothing; printing it and expecting order is a common surprise.
- Building with n pushes when heapify would do. Both give a valid heap, but one is O(n log n) and the other O(n).
- Forgetting that Python's heapq is a min-heap only. Negate the values, or wrap them in a tuple with an inverted key, and say so out loud.
- Sifting down to an arbitrary child. You must descend into the smaller child in a min-heap, or the sibling ends up out of order.