Two Heaps Pattern
Keep two heaps balanced. The tops give you the median at every step.
Quick Summary
Best used when
- ✓You need the median of a dynamically growing stream of numbers
- ✓You need to track the largest element in the smaller half and the smallest element in the larger half
- ✓The problem partitions elements into two groups and needs the boundary value quickly
- ✓Sorting the whole input after each insertion would be too slow
Clue words in the problem
Complexity gain
O(n log n) full-sort after each insert → O(log n) insert with O(1) median
Why this pattern matters for LeetCode
Finding the median of a stream seems to require sorting after every new number: O(n log n) per insert. Two heaps cuts that to O(log n) per insert. The trick: keep the smaller half in a max-heap and the larger half in a min-heap. The tops of the two heaps are always the two middle values. Rebalance whenever the sizes differ by more than one. The median is either the top of the larger heap, or the average of both tops.
What the Two Heaps pattern is
Two heaps maintains two priority queues: a max-heap holding the smaller half of the elements and a min-heap holding the larger half. When you insert a new element, place it into the correct heap and then rebalance so the size difference is at most one. The max-heap top is the largest element in the smaller half; the min-heap top is the smallest element in the larger half. Together they give O(1) median access and O(log n) insertions.
Reach for Two Heaps when you catch yourself thinking…
These are the internal questions that signal this pattern.
Beginner mental model
Imagine sorting a line of people by height and finding the person in the middle. As new people join, you don't re-sort everyone. Instead you keep two groups: a 'shorter half' and a 'taller half'. The tallest person in the shorter half and the shortest person in the taller half bracket the median. A max-heap gives you the tallest of the shorter group instantly; a min-heap gives you the shortest of the taller group instantly. Rebalancing means moving one person between groups when the groups become uneven.
Ready to practice Two Heaps?
Guided hints, not answers. You build real intuition.
When to use Two Heaps
- ✓Find Median from Data Stream — classic two-heap problem
- ✓Sliding Window Median — two heaps with element removal as the window moves
- ✓IPO / task scheduling where you want to greedily pick the best available item while staying within a constraint
- ✓Any problem that needs the maximum of the 'below median' elements and minimum of the 'above median' elements simultaneously
When NOT to use Two Heaps
- ✕You only need the overall median once — sort the array instead, O(n log n)
- ✕You need a specific percentile rather than the median — a sorted structure or order statistics tree is cleaner
- ✕The dataset is static and never grows — sort once and index the midpoint
- ✕You only need the maximum or minimum, not both simultaneously — a single heap is enough
Better alternative: If you only need one extreme (max or min), a single heap or sorted array suffices. For static data, just sort and index.
JavaScript code template
Running median — O(log n) addNum, O(1) findMedian
// JavaScript doesn't have a built-in heap — this shows the logic.
// In an interview use a MinHeap/MaxHeap class or a library.
class MedianFinder {
constructor() {
this.lowerMax = new MaxHeap(); // max-heap for the smaller half
this.upperMin = new MinHeap(); // min-heap for the larger half
}
addNum(num) {
// Route to the correct heap
if (this.lowerMax.isEmpty() || num <= this.lowerMax.peek()) {
this.lowerMax.push(num);
} else {
this.upperMin.push(num);
}
// Rebalance: sizes should differ by at most 1
if (this.lowerMax.size() > this.upperMin.size() + 1) {
this.upperMin.push(this.lowerMax.pop());
} else if (this.upperMin.size() > this.lowerMax.size()) {
this.lowerMax.push(this.upperMin.pop());
}
}
findMedian() {
if (this.lowerMax.size() === this.upperMin.size()) {
return (this.lowerMax.peek() + this.upperMin.peek()) / 2;
}
return this.lowerMax.peek(); // lowerMax always has the extra element
}
}addNum routes the new element: if it belongs in the lower half, push to lowerMax; otherwise push to upperMin. Then rebalance by moving the top element from the larger heap to the smaller one if the difference exceeds 1. findMedian is O(1): for odd total size, lowerMax has the extra element and its top IS the median. For even size, average the two tops.
Step-by-step thinking process
Walk through this in your head before writing code.
- 1Create a max-heap (lower half) and a min-heap (upper half).
- 2To add a number: compare to the max-heap's top to decide which heap it belongs in. Push it there.
- 3Rebalance: if one heap is larger by more than 1, move its top element to the other heap.
- 4After rebalancing, the invariant holds: lowerMax.size() >= upperMin.size() and lowerMax.size() <= upperMin.size() + 1.
- 5To find the median: if sizes are equal, average the two tops. If lowerMax is larger, its top is the median.
- 6Every addNum is O(log n); every findMedian is O(1).
“This app finally made it click. I tried NeetCode, CTCI, and YouTube for years and still couldn't solve Two Sum. The beginner mental models for the patterns are genuinely so helpful. THANK YOU.”
LeetCode-style problems that use this pattern
Practice on DSA Trainer with guided hints: no spoilers, just nudges.
Coming soon
- Find Median from Data StreamComing soon
The defining two-heap problem: addNum inserts, findMedian reads the tops of the two balanced heaps.
- Sliding Window MedianComing soon
Two heaps with lazy deletion: track elements leaving the window and ignore them during rebalance.
- IPOComing soon
Max-heap of available project profits + min-heap of project capital requirements — pick greedily each round.
Common mistakes beginners make
- ✕Forgetting to rebalance after every insertion — the median will be wrong as soon as one heap becomes too large.
- ✕Routing to the wrong heap — a new element must be compared to the current max-heap top, not a fixed value, to decide which half it belongs in.
- ✕Using a sorted array instead — insertion into a sorted array is O(n). Heaps give O(log n) insertion, which matters for streams.
- ✕Assuming JavaScript has a built-in heap — it doesn't. You'll need to implement one or use an abstract description in interviews. Practice the heap push/pop logic separately.
Two Heaps vs Sorted Array for Running Median
Choose the right tool for the job.
| Pattern | Use when… |
|---|---|
| Two Heaps | Numbers arrive in a stream and you need the median after each insertion in O(log n) |
| Sorted Array / Binary Search | The dataset is static or small enough that O(n log n) sort + O(1) index is acceptable |
Time and space complexity
Time complexity
O(log n) per insertion, O(1) per median query
Space complexity
O(n) to store all n elements across the two heaps
Each insertion triggers at most one push and one pop on each heap. Heap push and pop are O(log n). The rebalance step moves at most one element, also O(log n). The median query just peeks at the tops of both heaps — O(1). Total space is O(n) since every element is stored in exactly one of the two heaps.
Ready to practice Two Heaps?
Guided hints, not spoilers. 150 problems across 33 patterns. Five are free, the rest unlock with a founding member seat.
Frequently asked questions
- Why does a max-heap hold the smaller half?
- The max-heap gives O(1) access to the largest element in the smaller half — the left midpoint. The min-heap gives O(1) access to the smallest element in the larger half — the right midpoint. Choosing which heap type for which half is exactly this: max-heap for the left half so you can read the boundary quickly.
- What does rebalancing do and when is it needed?
- Rebalancing moves an element from the larger heap to the smaller one whenever the size difference exceeds 1. This maintains the invariant that lowerMax always has either the same count as upperMin or exactly one more. Without rebalancing the tops no longer represent the true median.
- Does JavaScript have a built-in heap?
- No. For interview purposes, describe the heap operations (push O(log n), pop O(log n), peek O(1)) and implement the two-heap logic. In practice, implement a simple binary heap class or use a priority queue library. Python has heapq; Java has PriorityQueue; C++ has priority_queue.
- When is lowerMax bigger and when are they equal?
- After inserting an odd number of elements, lowerMax has one extra (total size is odd, one heap must have the extra). After inserting an even number, both heaps are equal size. The invariant is lowerMax.size() === upperMin.size() or lowerMax.size() === upperMin.size() + 1. Never the other way.