Introduction to Heap
A heap works by enforcing one weak invariant — every parent dominates its children (≥ for max-heap, ≤ for min-heap) — on top of a shape invariant (complete binary tree), which together let it live in a plain array with no pointers and guarantee the extreme element is always at index 0, while insert/remove only need to fix a single root-to-leaf path rather than re-sort anything.
Recognize the pattern
- Problem asks for repeated access to the current min/max while the set keeps changing ("k largest so far", "running median", "next earliest event").
- You need a priority queue: process items by priority, not insertion order (Dijkstra, task scheduling, merge k sorted lists).
- You only ever need the top-1 element fast, not the full sorted order — sorting everything would be wasted work.
- Keywords: "top-k", "k-th largest/smallest", "nearest k points", "merge k lists".
Brute force → optimal
Brute force A (unsorted array): insert is O(1) append, but find-min/max scans everything: O(n) per query.
Brute force B (fully sorted array): find-min is O(1), but insert costs O(n) to shift elements into place. You've just moved the cost, not removed it.
Optimal (heap): accept a weaker order — only parent–child, never sibling–sibling. This buys O(1) peek and O(log n) insert/remove, because restoring the invariant only ever touches one root-to-leaf path, never the whole array.
Array representation
A heap is stored via level-order (BFS) traversal in an array — no pointers needed. For a node at index i: left child is at 2i+1, right child at 2i+2, parent at (i-1)/2 (integer division). Because the tree is always complete, these formulas always land on a valid, contiguous slot — that's the whole trick that makes arrays work here.
Complexity from first principles
Height: a complete binary tree with n nodes has height h = ⌊log₂ n⌋, because each level doubles the node count and the array packs levels contiguously with no gaps.
Insert (sift-up): the new element starts at a leaf and, in the worst case, swaps once per level on the way to the root. Number of comparisons/swaps ≤ h = O(log n). Peek: O(1) — it's just array[0].
Extract-min/max (sift-down): move the last element to the root (O(1)), then at each of h levels compare with (up to) 2 children and swap with the larger/smaller one: O(1) work × h levels = O(log n).
Build-heap from n elements: naive "insert n times" gives O(n log n). But bottom-up heapify (sift-down starting from the last internal node) is O(n): most nodes are near the bottom where sift-down does almost no work; summing cost×count over levels gives ∑ n/2^(h+1) · h = O(n), not O(n log n).
Space: O(n) for the array, O(1) extra for insert/extract (in-place swaps), O(log n) if sift-down/up is written recursively (call stack) — use the iterative form to keep it O(1).
Worked example: inserting 5 into a max-heap
Heap array: [16,14,10,8,7,9,3]. Insert 5 at the end, then sift up.
| Step | Array state | Action |
|---|---|---|
| 1 | [16,14,10,8,7,9,3,5] | Append 5 at index 7 (last position) |
| 2 | parent(7)=(7-1)/2=3 → value 8 | 8 ≥ 5, heap property holds → stop |
Now extract-max on [16,14,10,8,7,9,3]:
| Step | Array state | Action |
|---|---|---|
| 1 | [3,14,10,8,7,9] | Save 16 as result; move last element (3) to root; shrink array |
| 2 | [14,3,10,8,7,9] | children of 3 (idx0) are 14,10 → swap with larger child 14 |
| 3 | [14,8,10,3,7,9] | 3 now at idx1; children 8,7 → swap with larger child 8 |
| 4 | final | 3 at idx3 is a leaf → stop. Result: 16 removed, heap restored |
public class MinHeap {
private int[] a;
private int size;
public MinHeap(int capacity) { a = new int[capacity]; size = 0; }
public int peek() {
if (size == 0) throw new java.util.NoSuchElementException("heap is empty");
return a[0];
}
public void insert(int val) {
a[size] = val;
int i = size++;
while (i > 0) {
int parent = (i - 1) / 2;
if (a[parent] <= a[i]) break;
swap(parent, i);
i = parent;
}
}
public int extractMin() {
if (size == 0) throw new java.util.NoSuchElementException("heap is empty");
int min = a[0];
a[0] = a[--size];
siftDown(0);
return min;
}
private void siftDown(int i) {
while (true) {
int left = 2 * i + 1, right = 2 * i + 2, smallest = i;
if (left < size && a[left] < a[smallest]) smallest = left;
if (right < size && a[right] < a[smallest]) smallest = right;
if (smallest == i) break;
swap(i, smallest);
i = smallest;
}
}
private void swap(int x, int y) { int t = a[x]; a[x] = a[y]; a[y] = t; }
}
Pitfalls
- Unguarded
peek/extracton an empty heap — without an explicit size check, extract decrements size to -1 and corrupts the heap's state (or reads garbage); throwNoSuchElementExceptioninstead of failing dirty. - Forgetting to move the last element to the root before sift-down during extraction — using the wrong element corrupts the heap silently.
- Using
(i-1)/2in a language with floor-toward-negative-infinity division on negative numbers, or forgetting integer division truncates — off-by-one parent lookups. - Assuming a heap is sorted: iterating the array in index order does not give sorted output; only repeated extraction does.
- Recursive sift-up/down blowing the stack or adding hidden O(log n) space on very large heaps — prefer iterative.
- Comparator bugs in language built-ins (e.g., Java's
PriorityQueueis a min-heap by default; forgetting to passCollections.reverseOrder()for max-heap behavior).
When to use / when NOT — trade-offs
| Structure | Peek | Insert | Extract-top | Sorted iteration |
|---|---|---|---|---|
| Heap | O(1) | O(log n) | O(log n) | No (O(n log n) to drain) |
| Sorted array | O(1) | O(n) | O(1) (from end) | Yes, free |
| Balanced BST / TreeMap | O(log n) | O(log n) | O(log n) | Yes, in-order |
Use a heap when you need fast repeated access to only the current extreme value and don't care about full ordering (priority queues, top-k, Dijkstra) — and note that two heaps back-to-back solve running median: a max-heap of the lower half plus a min-heap of the upper half gives O(log n) insert and O(1) median. Prefer a sorted structure or balanced BST when you need range queries, predecessor/successor lookups, or full sorted traversal — a heap can't do those efficiently since it only orders parent–child, not siblings.
Takeaways
- Heap = complete binary tree + parent-dominance invariant, stored pointer-free in an array via index arithmetic.
- Peek is O(1); insert and extract are O(log n) because they only touch one root-to-leaf path.
- Building a heap from n elements bottom-up is O(n), not O(n log n) — don't insert one-by-one if you have all elements upfront.
- A heap gives you the top element fast but is not a substitute for a sorted structure or a BST when you need ordering beyond the root.
Recall: Why is bottom-up heapify O(n) instead of O(n log n) even though each sift-down is O(log n) in the worst case?
L0 · A heap is a complete binary tree that maintains the parent dominance invariant to support O(1) peek and O(log n) extract-min/max.
L1 · ⑤ Adversary/Edge — “If you insert N elements one-by-one into an empty binary heap, what is the overall time complexity?”
Trap: It is O(N) because heap construction is linear.
Bar: Bottom-up heapify (building from an array of size N) is O(N), but inserting N elements one-by-one takes O(N log N) time because each insertion can walk up the height of the heap. Heap Operations
L2 · ② Failure — “You are implementing a binary heap in an array. What happens if you run index arithmetic (like parent = (i-1)/2) on a 0-indexed array?”
Trap: It works perfectly for all indices.
Bar: Calculating parent for i = 0 yields i = 0 (or -1 in languages with negative division), causing infinite loops; add an explicit boundary check if (i == 0) return; to prevent index calculation bugs. Heap Operations
L3 · ③ Scale — “You are running Dijkstra's algorithm on a graph with V = 10⁵ and E = 10⁶. The heap size grows with every relaxed edge. How do you prevent memory exhaustion?”
Trap: Unconditionally insert every relaxed edge into the heap.
Bar: This allows the heap to grow to size O(E); maintain a mapping of node -> heap_index and use the decreaseKey operation to update node distances in place, keeping heap size bounded by O(V). Heap Operations
L4 · ① Concurrency — “How do you implement a concurrent Priority Queue using a binary heap without bottlenecking threads on a single lock?”
Trap: Synchronize the insert and extract-min methods.
Bar: A single lock serializes all operations; use skip lists or randomized heap structures that allow lock-free atomic pointer operations to avoid full heap serialization. Introduction to Heap
L5 · ⑥ Cost/Simplicity — “If we want to find the top-K elements in a stream of N numbers, why use a min-heap of size K instead of sorting the stream?”
Trap: Sorting the stream is too slow.
Bar: Sorting the entire stream takes O(N log N) time and O(N) space; a min-heap of size K processes the stream in O(N log K) time and O(K) space, which is highly efficient when K << N. Introduction to Heap
The floor keeps dropping: How do you implement a d-ary heap (where each node has d children) and what is the optimal choice of d for Dijkstra's algorithm?
Self-locate: died at L1 → you present mid-level; L4+ → staff signal.
Facing any new concept? Hit it with the six: concurrent? failing? at 100×? over time? adversarial? worth the cost? — that's the interviewer's whole playbook.
Synthesized from CLRS (Cormen et al., Introduction to Algorithms), Sedgewick & Wayne's Algorithms, and standard heap/priority-queue treatments used in interview-prep references.
🤖 Don't fully get this? Learn it with Claude
Stuck on Introduction to Heap? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Introduction to Heap** (DSA) and want to truly understand it. Explain Introduction to Heap from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Introduction to Heap** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Introduction to Heap** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Introduction to Heap** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.