295. Find Median from Data Stream
hardAsked at GoogleMaintain the median of a stream of numbers with O(log n) addNum and O(1) findMedian. Google asks this to test whether you reach for two heaps (max-heap of lower half + min-heap of upper half) and can articulate the size-balance invariant.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Source citations
Public interview reports confirming this problem appears in Google loops.
- Glassdoor (2026-Q1)— Google L4/L5 onsite reports cite this as the data-structure design round.
- Blind (2025-11)— Recurring Google senior IC interview problem.
Problem
The median is the middle value in an ordered integer list. Implement the MedianFinder class: addNum(num) - adds a number from the stream, findMedian() - returns the median of all elements so far.
Constraints
-10^5 <= num <= 10^5There will be at least one element in the data structure before calling findMedian.At most 5 * 10^4 calls will be made to addNum and findMedian.
Examples
Example 1
MedianFinder(); addNum(1); addNum(2); findMedian(); addNum(3); findMedian()[null,null,null,1.5,null,2.0]Approaches
1. Sorted array with binary-search insert
Maintain a sorted array. Binary-search the insertion point, splice the new value.
- Time
- O(n) addNum, O(1) findMedian
- Space
- O(n)
class MedianFinderSorted {
constructor() { this.data = []; }
addNum(num) {
let lo = 0, hi = this.data.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (this.data[mid] < num) lo = mid + 1;
else hi = mid;
}
this.data.splice(lo, 0, num);
}
findMedian() {
const n = this.data.length;
return n % 2 ? this.data[n >> 1] : (this.data[n / 2 - 1] + this.data[n / 2]) / 2;
}
}Tradeoff: Splice is O(n) — fails the spec's expected O(log n) for addNum. Mention only to anchor.
2. Two heaps (optimal)
Max-heap for the lower half + min-heap for the upper half. Maintain |sizes| <= 1. Median is heap top (odd total) or average of both tops (even total).
- Time
- O(log n) addNum, O(1) findMedian
- Space
- O(n)
class MaxHeap {
constructor() { this.data = []; }
push(v) { this.data.push(v); this._up(this.data.length - 1); }
pop() {
if (!this.data.length) return undefined;
const top = this.data[0];
const last = this.data.pop();
if (this.data.length) { this.data[0] = last; this._down(0); }
return top;
}
peek() { return this.data[0]; }
size() { return this.data.length; }
_up(i) {
while (i > 0) {
const p = (i - 1) >> 1;
if (this.data[p] >= this.data[i]) break;
[this.data[p], this.data[i]] = [this.data[i], this.data[p]];
i = p;
}
}
_down(i) {
while (true) {
const l = 2*i+1, r = 2*i+2;
let best = i;
if (l < this.data.length && this.data[l] > this.data[best]) best = l;
if (r < this.data.length && this.data[r] > this.data[best]) best = r;
if (best === i) break;
[this.data[best], this.data[i]] = [this.data[i], this.data[best]];
i = best;
}
}
}
class MinHeap extends MaxHeap {
_up(i) {
while (i > 0) {
const p = (i - 1) >> 1;
if (this.data[p] <= this.data[i]) break;
[this.data[p], this.data[i]] = [this.data[i], this.data[p]];
i = p;
}
}
_down(i) {
while (true) {
const l = 2*i+1, r = 2*i+2;
let best = i;
if (l < this.data.length && this.data[l] < this.data[best]) best = l;
if (r < this.data.length && this.data[r] < this.data[best]) best = r;
if (best === i) break;
[this.data[best], this.data[i]] = [this.data[i], this.data[best]];
i = best;
}
}
}
class MedianFinder {
constructor() {
this.lo = new MaxHeap(); // lower half
this.hi = new MinHeap(); // upper half
}
addNum(num) {
this.lo.push(num);
this.hi.push(this.lo.pop()); // funnel through to keep ordering
if (this.hi.size() > this.lo.size()) {
this.lo.push(this.hi.pop());
}
}
findMedian() {
if (this.lo.size() > this.hi.size()) return this.lo.peek();
return (this.lo.peek() + this.hi.peek()) / 2;
}
}Tradeoff: Two heaps split the stream. Funneling every new number through both heaps guarantees correct partitioning. Size-balance invariant: |lo| - |hi| in {0, 1}.
Google-specific tips
Google interviewers grade this on whether you can articulate the two-heap invariants BEFORE writing code: (1) lo is a max-heap holding the smaller half, hi is a min-heap holding the larger half; (2) lo.size() - hi.size() is always 0 or 1; (3) every element in lo is <= every element in hi. If you can state all three, the implementation falls out.
Common mistakes
- Forgetting to funnel new numbers through both heaps — adding directly to one breaks the ordering invariant.
- Maintaining the size-balance the wrong direction (allowing |hi| > |lo|).
- Off-by-one when computing the median for even total size.
Follow-up questions
An interviewer at Google may pivot to one of these next:
- What if you only cared about a sliding window of the last k numbers?
- What if you needed the k-th smallest, not just the median?
- How would you handle 100B numbers (out-of-core)?
Solve it now
Free. No sign-up. Python and JavaScript run instantly in your browser.
FAQ
Why two heaps and not one balanced BST?
A balanced BST would work (O(log n) per insert) but heaps are simpler to implement and have better constants. The two-heap split is also conceptually clearer: 'lower half' and 'upper half.'
Why funnel through both heaps?
Funneling guarantees that the new number ends up in the correct half. If lo has 3 elements and hi has 3 elements, blindly pushing to lo (without funneling) might place a large number in lo, breaking the invariant.
Practice these live with InterviewChamp.AI
Drill Find Median from Data Stream and other Google interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.
Practice these live with InterviewChamp.AI →