Find Median from Data Stream
Running median with two heaps — max-heap lows + min-heap highs, balance rules, and JS heap sketch.
advanced3 min read
- dsa
- heap
- interview
- Meta
- Amazon
The problem
Design a structure:
addNum(num)findMedian()— median of all numbers so far
If even count, average of two middle values (float ok).
add 1, add 2 → median 1.5
add 3 → median 2
Brute
Keep a sorted array; binary insert O(n). Median O(1). Fine for small streams.
class MedianFinderBrute {
private a: number[] = [];
addNum(num: number): void {
let i = 0;
while (i < this.a.length && this.a[i] < num) i++;
this.a.splice(i, 0, num);
}
findMedian(): number {
const n = this.a.length;
const m = n >> 1;
return n % 2 ? this.a[m] : (this.a[m - 1] + this.a[m]) / 2;
}
}
| add | O(n) | | median | O(1) |
Optimal: two heaps
- lo: max-heap of lower half
- hi: min-heap of upper half
Invariants:
- Every value in
lo≤ every value inhi - sizes differ by at most 1
Median: if sizes equal → average of tops; else top of the larger half.
JS has no built-in heap — use a simple binary heap class in interviews.
class Heap {
private data: number[] = [];
constructor(private cmp: (a: number, b: number) => boolean) {} // true if a should be above b
get size() {
return this.data.length;
}
peek(): number {
return this.data[0];
}
push(x: number) {
this.data.push(x);
this.up(this.data.length - 1);
}
pop(): number {
const top = this.data[0];
const last = this.data.pop()!;
if (this.data.length) {
this.data[0] = last;
this.down(0);
}
return top;
}
private up(i: number) {
while (i > 0) {
const p = (i - 1) >> 1;
if (!this.cmp(this.data[i], this.data[p])) break;
[this.data[i], this.data[p]] = [this.data[p], this.data[i]];
i = p;
}
}
private down(i: number) {
const n = this.data.length;
while (true) {
let best = i;
const l = i * 2 + 1;
const r = l + 1;
if (l < n && this.cmp(this.data[l], this.data[best])) best = l;
if (r < n && this.cmp(this.data[r], this.data[best])) best = r;
if (best === i) break;
[this.data[i], this.data[best]] = [this.data[best], this.data[i]];
i = best;
}
}
}
class MedianFinder {
private lo = new Heap((a, b) => a > b); // max-heap
private hi = new Heap((a, b) => a < b); // min-heap
addNum(num: number): void {
if (!this.lo.size || num <= this.lo.peek()) this.lo.push(num);
else this.hi.push(num);
// rebalance
if (this.lo.size > this.hi.size + 1) this.hi.push(this.lo.pop());
else if (this.hi.size > this.lo.size) this.lo.push(this.hi.pop());
}
findMedian(): number {
if (this.lo.size > this.hi.size) return this.lo.peek();
return (this.lo.peek() + this.hi.peek()) / 2;
}
}
| addNum | O(log n) |
| findMedian | O(1) |
| space | O(n) |
Balance rules (say out loud)
After insert into the natural side, if lo has 2+ more than hi, move max of lo to hi. If hi is larger, move min of hi to lo. Prefer lo as the larger half for odd counts.
Edge cases
- Single element
- All equal
- Decreasing stream
- Even/odd switching
Common mistakes
- Forgetting rebalance
- Min/max heap swapped
- Integer division for even median
Interview delivery
- Stream median → two heaps.
- Invariants.
- Implement or sketch heap.
- Trace 1,2,3.
- O(log n) add.
Related
- K Closest Points to Origin
- Top K Frequent Elements
- Sliding Window Maximum
- LRU Cache
- JavaScript Interview Guide