Keep the K best items in a heap so you never sort more than you need.
What the heap / top-K pattern is
A heap gives O(log n) insert and O(log n) extract of the minimum (or maximum), with the extreme readable in O(1). That makes it the tool whenever you repeatedly need 'the best remaining thing': the k largest elements, the next smallest among several streams, the most frequent items.
The counterintuitive core: to track the k LARGEST elements, use a MIN-heap of size k. Its root is the weakest of your current champions - the gatekeeper. Each new element fights the gatekeeper: bigger means swap in (pop, push), otherwise discard. After one pass the heap holds exactly the top k, in O(n log k) instead of O(n log n) for full sorting.
How to recognize it
Signals: 'k largest / smallest / closest / most frequent', 'merge k sorted lists/streams', 'schedule the next available', or streaming data where a full sort is impossible because elements keep arriving. If k is small relative to n, the heap's O(n log k) is the point; if k ~ n, just sort.
Know the alternatives to name: quickselect finds the k-th element in average O(n) when a one-shot answer suffices (no streaming), and bucket sort by count solves top-k-frequent in O(n) when frequencies are bounded by n. Offering the trade-off is worth as much as the heap code.
The size-k gatekeeper template
Build nothing up front: push elements until size k, then for each remaining element compare against the root. The heap never exceeds k entries, so memory is O(k) - which is why this works on streams that never end.
pseudocode
# k largest elements of a stream
heap = min-heap
for x in stream:
if len(heap) < k:
heap.push(x)
elif x > heap.peek(): # beats the weakest champion
heap.pop()
heap.push(x)
# heap now holds the k largest; root is the k-th largest
K-way merging
To merge k sorted lists, the only candidates for 'next smallest overall' are the k current heads. Keep them in a min-heap: pop the smallest, append it to the output, push its successor from the same list. Every element passes through the heap once: O(N log k) for N total elements - the heap's size is k, not N.
Common pitfalls
Flipping the heap direction: k largest needs a min-heap (evict the weakest), k smallest needs a max-heap. Getting this backwards inverts the answer.
Heapifying all n elements (O(n) memory, O(n log n) drain) when the size-k gatekeeper does it in O(k) memory - the difference is the entire pattern.
Languages with only min-heaps (Python): negate values or use tuple keys for max-heap behavior, and say you are doing it.
Pushing non-comparable tuples: when priorities tie, the heap compares the next tuple field - include an index tiebreaker before any non-comparable payload (classic merge-k-lists crash).
Ignoring quickselect when the interviewer asks for 'better than O(n log k)' on a static array - average O(n) is the expected answer.
Worked examples
1. Kth largest element in an arraymedium
Find the k-th largest element of an unsorted array without fully sorting it.
Full sort is O(n log n) and computes far more order than needed - only the boundary between top-k and the rest matters.
Run the gatekeeper: a min-heap capped at size k; each element larger than the root replaces it.
After the pass, the root is exactly the k-th largest - the weakest member of the top-k club.
O(n log k) time, O(k) space. Follow-up to volunteer: quickselect achieves average O(n) on static data, but the heap wins on streams and gives worst-case bounds.
pseudocode
heap = min-heap
for x in nums:
heap.push(x)
if len(heap) > k:
heap.pop() # evict the weakest
return heap.peek()
Remember: The min-heap's root is the k-th largest by construction - it is the doorman deciding who stays in the top-k club.
2. Top K frequent elementsmedium
Return the k most frequent elements of an array.
First a hash map of element -> count: O(n). The problem is now top-k over (count, element) pairs.
Apply the gatekeeper keyed on count: min-heap of size k over the map's entries, O(m log k) for m distinct values.
Pop everything at the end for the answer (order among the k is usually free to choose).
Name the O(n) alternative: bucket sort - index buckets by count (a count never exceeds n), then walk buckets from high to low collecting k elements.
pseudocode
counts = hash map of value -> frequency
heap = min-heap ordered by count
for (value, count) in counts:
heap.push((count, value))
if len(heap) > k: heap.pop()
return values remaining in heap
Remember: Count first, then the problem is generic top-k on the counts - and when counts are bounded by n, buckets beat the heap outright.
3. Merge k sorted listshard
Merge k sorted linked lists into a single sorted list.
At any moment, the next output node is the smallest of the k current heads - a rolling minimum over k candidates, i.e. a min-heap of size k.
Seed the heap with each non-empty list's head. Loop: pop the minimum, append to the output tail (dummy-head trick), push the popped node's successor if any.
Add an index tiebreaker to the heap tuples so equal values never force comparison of list nodes.
Every one of the N total nodes is pushed and popped once: O(N log k) time, O(k) space. Compare aloud with repeated pairwise merging - O(N k) - and divide-and-conquer pairing, which matches the heap's bound.
pseudocode
heap = min-heap of (node.val, i, node) for each list head
dummy = Node(); tail = dummy
while heap not empty:
(val, i, node) = heap.pop()
tail.next = node; tail = node
if node.next: heap.push((node.next.val, i, node.next))
return dummy.next
Remember: K sorted sources reduce to one heap of their heads: pop the global minimum, refill from the same source, repeat - the heap stays k-sized while N flows through it.
Check yourself
Try to answer before revealing - retrieving from memory is what makes it stick.
1Why does finding the k largest elements use a MIN-heap?
Min-heaps are faster than max-heaps
✓ The root is the weakest of the current top-k, which is exactly the element that should be evicted when something bigger arrives
It avoids duplicate elements
It doesn't - a max-heap is required
The heap stores your champions; the eviction decision always concerns the weakest of them. A min-heap surfaces that element in O(1), making each keep-or-discard decision one comparison plus at most one O(log k) replace.
2What is the complexity of the size-k gatekeeper over n elements, and why does it matter?
O(n log n), same as sorting
✓ O(n log k) time and O(k) space - it beats sorting when k is small and works on unbounded streams
O(k log n) time
O(n + k) always
Each of n elements does at most one O(log k) heap operation, and memory never exceeds k. Small k makes log k tiny, and O(k) memory is what makes streaming feasible at all.
3In merge-k-lists, why is the heap's size k rather than N?
✓ Because the lists are sorted, only the k current heads can be the next smallest - the heap holds candidates, not all elements
Because N wouldn't fit in memory
The heap is actually size N
Because k is always smaller than log N
Sortedness guarantees each list's head is its smallest remaining element, so the global next-smallest is among the k heads. Elements stream through a k-sized heap: N pushes, N pops, O(N log k).
4When does quickselect beat the heap for a top-k question?
Never - heaps are always optimal
✓ On a static in-memory array needing a one-time answer: average O(n) beats O(n log k); heaps win for streams or when worst-case bounds matter
When k = 1
When the array is already sorted
Quickselect partitions in place for average O(n) but needs all data present and mutable, has an O(n^2) worst case (mitigated by randomization), and yields no order among the top k. The heap trades a log factor for streaming and stability of bounds.
Frequently asked questions
How do I get a max-heap in a language that only ships a min-heap?
Negate the keys (push -x, negate on pop) or wrap values in a comparator/tuple that inverts order. State the trick explicitly in interviews - silent negation is a classic source of sign bugs.
What belongs in the heap tuple?
(priority, tiebreaker, payload) - the tiebreaker (usually an insertion index) guarantees comparability when priorities tie and payloads aren't comparable, and makes behavior deterministic. This is the fix for the merge-k-lists comparison crash.
Heap vs. balanced BST / sorted structure - when each?
A heap only ever surfaces the extreme, but does it with tiny constants - perfect for top-k and schedulers. Needing predecessor/successor queries, deletion of arbitrary elements, or sorted iteration points to an ordered map or two-heap arrangements instead.