Two heaps (running median)
In one line
Split the numbers into a max-heap for the smaller half and a min-heap for the larger half, keep their sizes within one, and the median is always at one or both tops: O(log n) per insert and O(1) to read the median.
What it is
The two-heaps pattern keeps a dividing line through the data. The 'low' heap is a max-heap holding the smaller half, so its top is the largest of the small numbers. The 'high' heap is a min-heap holding the larger half, so its top is the smallest of the large numbers. Those two tops sit right next to the middle.
Two invariants make it work: every number in low is less than or equal to every number in high, and the sizes differ by at most one (here, low may have one extra). With an odd count the median is low's top; with an even count it is the average of both tops.
The same idea applies whenever you need fast access to the boundary between two groups, such as the best affordable project in the IPO problem or a percentile other than 50%.
Why interviewers ask
Find Median from Data Stream (LeetCode 295) is a classic hard question, and Sliding Window Median (480) and IPO (502) reuse the pattern. Interviewers check that you can keep both invariants straight, remember that Python needs negation for a max-heap, compute the even-count median correctly, and explain why sorting on each insert (O(n) per insert) is worse.
How it works
- 1Keep low (max-heap, stored as negatives in Python) and high (min-heap).
- 2To add x: push it into low. Then pop low's largest and push it into high. This step guarantees every item in low is <= every item in high, even if x belonged in the upper half.
- 3Rebalance: if high now has more items than low, pop high's smallest and push it back into low. Now low has the same number of items as high or one more.
- 4Median: if low is bigger, return low's top. Otherwise return the average of low's top and high's top.
- 5Each add does a constant number of heap operations, so it is O(log n); reading the median is O(1).
Worked example · Stream 5, 15, 1, 3
- add 5: low = {5}. Move low's max 5 to high: low = {}, high = {5}. High is bigger, move 5 back: low = {5}, high = {}. Median = 5.
- add 15: low = {5, 15}. Move 15 to high: low = {5}, high = {15}. Sizes equal. Median = (5 + 15) / 2 = 10.
- add 1: low = {1, 5}. Move 5 to high: low = {1}, high = {5, 15}. High is bigger, move 5 back: low = {1, 5}, high = {15}. Median = low's top = 5.
- add 3: low = {1, 3, 5}. Move 5 to high: low = {1, 3}, high = {5, 15}. Sizes equal. Median = (3 + 5) / 2 = 4.
- Check against the sorted list [1, 3, 5, 15]: middle values 3 and 5, median 4. Correct.
Picture it
Imagine lining people up by height and splitting them into a short team and a tall team of equal size. You only need to know the tallest person on the short team and the shortest person on the tall team: the median height is right between them. When someone new arrives, you place them and move at most one person across the line.
Reference code
import heapq
class MedianFinder:
def __init__(self):
self.low = [] # max-heap of the smaller half (values stored negated)
self.high = [] # min-heap of the larger half
def add(self, x): # O(log n)
heapq.heappush(self.low, -x)
# move the largest of the low half up, so every low <= every high
heapq.heappush(self.high, -heapq.heappop(self.low))
if len(self.high) > len(self.low): # low keeps the extra item
heapq.heappush(self.low, -heapq.heappop(self.high))
def median(self): # O(1)
if len(self.low) > len(self.high):
return -self.low[0]
return (-self.low[0] + self.high[0]) / 2Every new number goes into low, then low's largest is moved to high, which keeps the ordering invariant. If high becomes bigger, its smallest moves back, so low always has equal size or one extra. The median is low's top for an odd count and the average of the tops for an even count.
Key patterns
Running median
When: Median of a growing stream (LeetCode 295)
How: Max-heap low, min-heap high, sizes within one, median from the tops.
Sliding window median
When: Median of every window of size k (LeetCode 480)
How: Two heaps plus lazy deletion: record outgoing values in a hash map and discard them when they surface at a top; track the valid sizes separately.
IPO / maximise capital
When: Pick up to k projects you can afford, best profit first (LeetCode 502)
How: Min-heap of projects by capital; move all affordable ones into a max-heap by profit; pop the best, add profit, repeat.
Any percentile
When: Running 90th percentile
How: Keep low at about 90% of the items and high at 10%; the answer is low's top. Rebalance to keep that ratio.
Real-world use
Latency monitoring
A service tracking the exact median response time over a moderate window can use two heaps. At very large scale, systems such as Elasticsearch's percentiles aggregation use approximate sketches like t-digest instead, trading exactness for bounded memory.
Trading and pricing
Rolling median prices are used as noise-resistant reference prices because one outlier trade moves a median far less than a mean; a two-heap structure with lazy deletion keeps it updated per tick.
Sensor and signal filtering
Median filters smooth spikes in sensor readings and images; streaming versions keep the window's values in two heaps or an ordered structure to update the median as each sample arrives.
Ride-hailing and delivery ETAs
Dashboards that show the median delivery or pickup time for a city in real time need a per-event update and an instant read, which is exactly the two-heaps trade-off.
In a project
You are building a fitness app feature that shows each user's median daily step count since they joined, updated live, for users with up to a few thousand days of data.
- Keep two heaps per user (or rebuild them from stored history when the user opens the screen).
- On each day's final count, add it in O(log n) with the push-move-rebalance routine.
- Read the median in O(1) whenever the screen renders.
- For a 'last 30 days' median, switch to the sliding-window version with lazy deletion, or simply sort 30 numbers, since the window is tiny.
Impact: The median updates instantly without re-sorting years of history, and it gives users a fairer picture of a typical day than the average, which one marathon day would skew.
Trade-offs
Use it when
- Numbers arrive one by one and you need the median (or a percentile) after each
- Inserts and reads are interleaved many times
- You need exact answers and the data fits in memory
Avoid it when
- You need the median only once: quickselect finds it in O(n) average
- Values must also be deleted often: a sorted container or balanced BST handles removal directly (two heaps need lazy deletion)
- Data is huge or distributed: use approximate sketches such as t-digest
Cost: add O(log n), median O(1), space O(n). Sliding window median with lazy deletion: O(n log n) overall (stale entries can briefly keep heaps larger than k).
Common mistakes
- Pushing a new number into one heap based only on size, which can break the rule that every low item is <= every high item.
- Forgetting to negate values when using Python's heapq as a max-heap, or forgetting to negate them back when reading.
- Returning an integer division for the even-count median in languages where / truncates.
- Letting the sizes differ by more than one, so the tops are no longer the middle values.
- Trying to remove arbitrary values from a heap directly in the sliding window version instead of using lazy deletion.
Interview questions
Quick revision
- low = max-heap (smaller half), high = min-heap (larger half).
- Invariant 1: max(low) <= min(high). Invariant 2: len(low) - len(high) is 0 or 1.
- Add: push to low, move low's top to high, move back if high is bigger.
- Odd count: median = top of low. Even: average of both tops.
- add O(log n), median O(1).
- Python max-heap: store negatives.
- Sliding window: lazy deletion; IPO: min-heap by cost + max-heap by profit.
Check yourself
1. In the two-heaps median structure, which heap holds the smaller half?
2. low = {1, 3}, high = {5, 15}. What is the median?
3. Why does the add routine always pass the new number through low and then into high?
4. What is the cost of adding a number?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.