The Heap
A container that always hands you the most important item first. It never sorts everything. It promises one thing only: the top is the current minimum (or maximum), and both taking that item out and putting a new one in cost O(log n). It is fast because it promises so little.
Why it exists: an emergency room is not first come, first served
When you only need to know who is most urgent, ordering everyone is wasted work
A canteen counter serves people in arrival order. That is the queue from the previous chapter. An emergency room cannot work that way. A patient with a heart attack who arrived five minutes ago goes in before someone who has been waiting an hour with a sprained ankle. The process is called triage: each time a room opens, staff take the most urgent patient. Nobody works out who is second or third most urgent, because the next decision will be made from scratch anyway.
That is the whole job of a heap. In a collection that keeps changing, it reports the minimum (or maximum) in O(1), and it removes that element or inserts a new one in O(log n). The phrase "keeps changing" matters: the data does not arrive all at once, just as patients keep walking in.
Why not simply sort everyone, so the most urgent stands first? You can, but sorting does far more work than the question asks for. Sorting costs O(n log n) and fixes the order of all n elements, while you only need one of them. It also breaks down under updates: inserting a new patient into a sorted array means shifting elements, O(n), which is the cost you met in chapter 1. A heap takes the opposite position. It only maintains which element is the extreme one, and that is why insert and remove are both O(log n).
Two words are often mixed up, so separate them now. A priority queue is an interface: a container you can keep adding to, and from which you can always remove the highest-priority element. A binary heap is the most common implementation of that interface. The relation is the same as between "sorting" and "quicksort". In almost every language, PriorityQueue and heapq are backed by a binary heap, and this chapter builds that engine. They are not synonyms, so this chapter says "heap" for the structure and "priority queue" for the interface.
A heap guarantees one thing: the top element is the smallest (or the largest) in the whole heap. The order of everything else, including two nodes at the same level, is not defined. Fewer promises, cheaper maintenance.
Inserting a value and removing the current extreme both follow one path down the height of the tree: O(log n). Reading the top without removing it is O(1). This is why a heap can handle a stream of data.
Task scheduling, Dijkstra's shortest path, Huffman coding, Top-K, merging k sorted sequences. Any problem that repeatedly asks for the current extreme value is usually running a heap.
One word, two meanings
A common source of confusion: the heap in this chapter has nothing to do with heap memory. They only share a name. Heap memory is the region where a running program allocates objects dynamically, as opposed to the call stack. The heap in this chapter is a data structure, a tree with a specific shape. When you read the word, check which one is meant.
The structure: two rules, and a place to live
It is a special binary tree that needs no pointers, because the whole tree fits into an array
A heap is a binary tree that follows only two rules. One controls the shape, the other controls the order.
In a complete binary tree, every level is full except possibly the last, and the nodes on the last level are packed to the left with no gap between them (chapter 7 introduced it). This rule keeps the tree short and wide: n nodes give a height of exactly ⌊log₂n⌋. It also means no position is skipped, which is what makes the array layout below possible.
Every parent is ≤ each of its children. Applying that rule down every path makes the root the smallest value in the tree, which is a min-heap. The rule constrains only the parent-child pair. Siblings are unrelated: the left child may be larger than the right child. Parent ≥ child gives a max-heap, where the root is the largest value. The two are mirror images.
The most common misunderstanding: a heap is not a sorted array
Many people picture a heap as "a tree sorted from small to large". It is not. A heap only guarantees that each parent → child pair is ordered. Two nodes at the same level, siblings or cousins, have no defined relationship at all. That is why printing the backing array often gives something like [1, 3, 2, 7, 4, 5]. It does not look sorted, but it is a valid heap: 1≤3, 1≤2, 3≤7, 3≤4, 2≤5. Every parent-child pair holds. Because a heap orders one chain at a time instead of the whole collection, an update costs O(log n) instead of the O(n log n) of a full sort.
Now the two rules connect. Rule 1 says a complete binary tree has no gap. So you can number the nodes level by level, left to right (the root is 0, the next level is 1 and 2, then 3, 4, 5, 6, and so on) and lay them straight into an array, using the number as the index. Because there is no gap, no array slot is wasted, and no pointer is needed: every parent-child link is computed from the index by three formulas.
| From index i, find | Formula | Example (i = 4) |
|---|---|---|
| Parent parent | (i − 1) / 2 (integer division, rounds down) | (4 − 1) / 2 = 1 |
| Left child left | 2i + 1 | 2×4 + 1 = 9 |
| Right child right | 2i + 2 | 2×4 + 2 = 10 |
You have seen this idea before. In the array chapter, the address of an element is base address + index × element size, also computed rather than stored. A heap applies the same arithmetic one level up, from array elements to tree links. Dropping the pointers buys two concrete things: no extra memory (a linked binary tree stores two pointers per node), and elements laid out contiguously in memory, which is friendlier to the CPU cache (the same benefit as in chapter 1).
Would starting at index 1 be simpler?
Some textbooks start the heap at index 1, which makes the formulas shorter: parent = i / 2, left = 2i, right = 2i + 1. The cost is that index 0 is unused. Real implementations, including Java's PriorityQueue and Python's heapq, start at 0 and waste nothing, so this chapter uses the 0-based formulas throughout. Both conventions are correct. Just do not mix them in one piece of code.
Core operations: sift up, sift down, and one surprising build
Try itEvery operation does the same thing: move the element that broke parent ≤ child back to where it belongs
A heap has two movements, and they mirror each other. On insert, the new element travels from the bottom upwards, which is called sift up. On removal, an element travels from the top downwards, which is called sift down. Push and pop a few values in the lab below and watch the tree view and the array view change together, since they show the same data. You will see that a heap operation is nothing more than comparing one parent-child pair and swapping when the rule is broken, repeated until no swap is needed.
Now in slow motion. push (insert) takes two steps:
- 1. Write at the end of the array, which is the next free slot of the complete binary tree. This step keeps the shape rule intact.
- 2. Sift up: compare the new value with its parent and swap while it is smaller, then compare with the new parent, and so on, until it is not smaller than its parent or it reaches the root. The longest path runs from the bottom level to the root, so the number of swaps is at most the height: O(log n).
pop (remove the extreme) is the mirror image, in three steps:
- 1. Read the root. That is the answer, the smallest value in a min-heap.
- 2. Move the last element to the root. Why the last one? Because removing the last slot keeps the tree complete, while removing any other position would leave a gap.
- 3. Sift down. The value that just arrived at the top is probably too large. Compare it with the smaller of its two children and swap while it is larger, following it down, until it is smaller than both children or it has no child. Again at most the height: O(log n).
Why must sift down pick the smaller child? Suppose you swapped with the larger child instead. That larger child becomes the parent, while its smaller sibling is still below it, so parent ≤ child is broken immediately. Only promoting the smaller child keeps both children below the new parent.
| Operation | Cost | Why |
|---|---|---|
| peek read the extreme without removing it | O(1) | The extreme is array slot 0. Reading it touches nothing else. |
| push insert | O(log n) | O(1) to write at the end, then sift up along one leaf-to-root path, at most the height |
| pop remove the extreme | O(log n) | Last element takes the root, then sifts down along one root-to-leaf path, at most the height |
| heapify turn an existing array into a heap | O(n) | Sift down from the bottom up; the total number of steps stays below n (explained below) |
| Build by pushing one by one | O(n log n) | n inserts at O(log n) each. Use heapify instead when you already hold all the data |
| Find or remove any value that is not the root | O(n) | A heap is not organised for locating a value in the middle, so the only way is a linear scan |
The surprising row is heapify: turning an unordered array into a heap in place costs only O(n). Intuition says "n elements, each may sift down log n levels, so O(n log n)". Press Build from random array in the lab and count the swaps. There are usually far fewer than expected. Here is why:
Why building a heap is O(n), not O(n log n)
The method is to sift down every node from the last internal node (index n/2 − 1) backwards to index 0, which is called Floyd's build-heap. The point is which nodes can travel far. In a complete binary tree, about half of the nodes are leaves, sitting on the bottom level and sifting down 0 levels. About a quarter sit one level higher and sift down at most 1 level, about an eighth at most 2, and so on. The deeper a node can sink, the rarer it is. Adding it all up gives Σ d · n / 2d+1, and since Σ d / 2d+1 = 1, the total is at most n swaps, so building the heap is O(n). Pushing one by one does the opposite: it makes every new element climb towards the root, and the elements that climb furthest are the numerous ones near the bottom. That difference is exactly one log factor. The rule to remember: if you already hold all the data, use heapify at O(n); if the data arrives one item at a time, you have no choice but to push.
Do not use a heap as an ordered container
A heap is good at repeatedly producing the extreme value. It is bad at three things. First, finding a specific value costs O(n). Second, producing all elements in order requires n pops, so O(n log n) in total. That procedure is exactly heap sort, which is O(n log n) in the best, average, and worst case, uses O(1) extra space, and is not stable: equal elements can come out in a different order than they went in. Third, a heap gives you only the root, so the general way to reach the k-th smallest value is to pop k − 1 times first. If you need ordered access or range queries at any time, go back to the BST and TreeMap of chapter 8. Choosing the wrong structure is the most expensive mistake in both interviews and production code.
Write a MinHeap: about 40 lines, nothing missing
push, pop, peek, siftUp, siftDown, heapify — commented line by line and ready to run
Here are the movements from §03 as code. There are three real methods: siftUp, siftDown, and the static heapify that builds a heap in O(n). push and pop only combine "change the array" with "repair the heap order". The storage is a plain resizable array, and every parent-child link comes from the three formulas in §02. Cover the code and try to write siftDown from memory. The step that picks the smaller child is the one people get wrong most often.
heapq is the same algorithm implemented in C (heappush, heappop, heapify). Writing it by hand here is only to see the inside. §05 shows how to use heapq directly.Check that you really got it
Close the code and answer three questions. 1. Why does pop move the last element to the root instead of one of the children? (Because only removing the last slot keeps the tree complete.) 2. When does if (m === i) break in siftDown trigger? (When the current node is already ≤ both children, or it has no child at all.) 3. How many places must change to turn this min-heap into a max-heap? (Two: the direction of the two comparisons.)
Three languages: two ship a heap, one does not
Java has PriorityQueue, Python has heapq, JavaScript has nothing built in
§04 exists so you understand the mechanism. When you solve problems, use what the language provides. The three differ a lot here. Java has PriorityQueue ready to use. Python has heapq, a set of functions that operate on a plain list. JavaScript has no built-in heap, which is why the previous section asked you to memorise one. Remember two shared traps: Java and Python both default to a min-heap, so a max-heap takes extra work; and iterating a heap does not give you sorted order, so the only way to read the elements in order is to remove them one at a time.
TypeError. The usual fix is to put an increasing counter in the middle as a tie-breaker, as in (freq, idx, obj). The counter is never repeated, so the comparison never reaches the object.| Operation | Java PriorityQueue | Python heapq | JavaScript (hand-written / package) | Cost |
|---|---|---|---|---|
| Create an empty min-heap | new PriorityQueue<>() | [] (used with heapq) | new MinHeap() | O(1) |
| Read the root peek | pq.peek() | h[0] | h.peek() | O(1) |
| Insert push | pq.offer(x) | heapq.heappush(h, x) | h.push(x) | O(log n) |
| Remove the root pop | pq.poll() | heapq.heappop(h) | h.pop() | O(log n) |
| Number of elements | pq.size() | len(h) | h.size() | O(1) |
| Build from existing data | new PriorityQueue<>(coll) | heapq.heapify(h) | MinHeap.heapify(a) | O(n) |
| Max-heap | Comparator.reverseOrder() | store negated values | pass a comparator / store negated values | — |
| Top-K shortcut | (maintain a heap of size k yourself) | heapq.nlargest(k, h) | (maintain a heap of size k yourself) | O(n log k) |
Patterns: Top-K and the heap that points the other way
★ Interview coreWhen a problem says k-th largest, top k, or merge k sorted lists, think heap. Three worked examples, frame by frame
Most heap problems fall into three patterns. 1. Top-K: the k-th largest or smallest value, the k most frequent items, the k nearest points. 2. Repeatedly take the extreme and put something back: each step greedily uses the current extreme value and then inserts an updated value, as in task scheduling, reorganising a string, or smashing stones. 3. Merge k sorted sequences: k linked lists or k rows of a matrix. The three worked examples below take one pattern each. Start with the part of Top-K that surprises everyone.
The Top-K rule: to find the k-th largest, use a min-heap
The first guess is usually "k-th largest, so use a max-heap". It is the other way round. The standard solution is a min-heap holding at most k elements. Once this clicks, most Top-K problems become the same problem. The reasoning: you want to keep the k largest values seen so far, and whenever a better value arrives you must evict the weakest of the k you already have. The weakest of those k is their minimum, and reading the minimum at any moment is exactly what a min-heap does in O(1). So the root becomes the entry threshold: a new value enters only if it is larger than the root, and the old root is removed to make room. After scanning all n values, the heap holds the k largest, and the root is the k-th largest. The mirror case: for the k-th smallest, use a max-heap of size k, whose root is the largest of the k candidates. The general rule: choose the heap that puts the element you would evict first at the root. Cost is O(n log k), which beats sorting at O(n log n) whenever k is much smaller than n.
LC 215 · Kth Largest Element in an Array
MEDIUMTask: return the k-th largest element of an array. Duplicates count, so it is not the k-th distinct value. Brute force: sort the whole array in O(n log n) and take the k-th from the end. That passes, but it computes the order of every element to answer a question about one. Standard solution: apply the rule above with a min-heap of size k, where the root is the threshold. Here is the run for k = 2.
Cost and follow-up
Time O(n log k): n values, each entering and leaving a heap of size k at most once. Space O(k). The classic follow-up is "can you do it in O(n)?" Yes, with quickselect: reuse the partition step of quicksort and recurse into one side only. That is O(n) on average and O(n²) in the worst case, which a random pivot makes very unlikely. So why is the heap solution still common? Two reasons. First, in a stream the values keep arriving and there is no complete array to partition. Second, when the data does not fit in memory, a heap of size k needs only O(k) space. A full answer explains both solutions and when each one applies.
LC 347 · Top K Frequent Elements
MEDIUMTask: return the k most frequent elements of an array. Brute force: count the frequencies, sort by frequency, and take the first k, at O(n log n). Standard solution: this is the first problem where you combine two structures. One structure is not enough, so chain two: a hash map counts each value in O(n) (chapter 6), and then those counts are fed into a min-heap of size k for the Top-K step (this chapter). Note that the heap is ordered by count, so the threshold is the lowest count still in the heap.
Cost and follow-up
Counting is O(n) and maintaining the heap is O(n log k), so the total is O(n log k), with O(n) space for the hash map. The follow-up is "can you do it in O(n)?" Yes, with bucket sort: no count can exceed n, so create n + 1 buckets and let bucket[f] hold every value that appears f times. Then collect from the highest bucket downwards until you have k values. No sorting and no heap are needed. Hash plus heap is the general solution that is easiest to remember; hash plus buckets is the O(n) special case.
LC 23 · Merge k Sorted Lists
HARDTask: merge k sorted linked lists into one sorted list. Brute force: copy every node value into an array, sort it, and rebuild the list, at O(N log N) where N is the total number of nodes. It works, but it throws away the fact that each list is already sorted. Standard solution: when merging sorted sequences, the next value of the answer is always the smallest among the current head nodes. Which structure reports the smallest of a changing set? A min-heap. So the heap holds only the k head nodes: take the smallest, then push the node that follows it.
val, Python compares the next item. If that item is the node itself and ListNode does not define <, you get a TypeError. Putting a unique increasing index i in between means the comparison never reaches the node. This is the standard way to store objects in heapq.Cost and follow-up
Let N be the total number of nodes. Each node enters and leaves the heap exactly once, and each heap operation costs O(log k), because the heap never holds more than k nodes. So the time is O(N log k) and the extra space is O(k). Notice the link to the linked list chapter: the heap stores nodes, not values, so next already points at the rest of that list and nothing has to be copied. The classic follow-up is "can you solve it without a heap?" Yes, by merging pairs of lists: repeatedly apply "merge two sorted lists" (LC 21). k lists take log k rounds, each round scans O(N) nodes in total, so it is also O(N log k) and it does not need the heap's O(k) space.
Problem set: 8 heap problems
Interview regularsFrom the Top-K threshold heap, to repeatedly taking the extreme, to merging k sequences and two heaps facing each other
Quiz
✎ QuizAnswer all 7 correctly to complete the chapter
Which of these does a min-heap guarantee? (Select all that apply.)
A heap is stored in an array. What is the index of the parent of index 7?
push and pop are O(log n). What is the reason?
To find the k-th largest element of an array, which heap does the standard solution use?
Python's heapq is a min-heap only. What is the usual way to get a max-heap?
You already have n elements in an array. What is the best way to turn them into a heap, and at what cost?
In Java, what order does for (int x : priorityQueue) produce?
- A heap promises one thing: the top is the extreme value. Siblings in any order and an unsorted backing array are both legal. Fewer promises means cheaper maintenance, O(log n) per update instead of O(n log n) for a full sort. That trade is the whole design.
- Two rules: a complete binary tree (short and wide, height ⌊log₂n⌋, no gaps) and parent ≤ child for a min-heap. Because there are no gaps, the whole tree fits into an array and every link is computed:
parent = (i−1)/2,children = 2i+1, 2i+2. No pointers. - push = write at the end + sift up. pop = read the root, move the last element up, + sift down. Both are O(log n), one path down the height. peek is O(1). Building from data you already hold uses heapify and is O(n), because the nodes that can sink far are the rare ones.
- The Top-K rule that reverses your intuition: for the k-th largest, use a min-heap of size k (the root is the threshold, and a value enters only if it is larger); for the k-th smallest, use a max-heap. Pick the heap that puts the element you would evict first at the root. Cost O(n log k).
- A priority queue is the interface; a binary heap is the usual implementation. In practice: Java
PriorityQueueis a min-heap by default and takes aComparatorsuch asreverseOrder()to flip it, and iterating it is unordered; Pythonheapqis a min-heap only, so a max-heap means storing negated values; JavaScript has nothing built in, so write your own or use a package.