Bird
Raised Fist0
DSA Javascriptprogramming

Median of Data Stream Using Two Heaps in DSA Javascript

Choose your learning style10 modes available
Mental Model
We keep two heaps to split numbers into smaller half and larger half so we can quickly find the middle value.
Analogy: Imagine sorting people by height into two lines: one line for shorter people and one for taller people. The middle person is between these two lines.
MaxHeap (smaller half)       MinHeap (larger half)
      [5, 3, 1]                  [6, 8, 9]
       ↑ top is max              ↑ top is min
Dry Run Walkthrough
Input: Stream: 5, 3, 8, 9, 1, 6
Goal: Find the median after each new number arrives
Step 1: Add 5 to max heap (smaller half)
MaxHeap: [5] ↑top
MinHeap: []
Why: Start by putting first number in smaller half
Step 2: Add 3 to max heap, then balance by moving max from max heap to min heap
MaxHeap: [3] ↑top
MinHeap: [5] ↑top
Why: Keep smaller half max heap larger or equal size than min heap
Step 3: Add 8 to min heap, then balance by moving min from min heap to max heap
MaxHeap: [5, 3] ↑top
MinHeap: [8] ↑top
Why: Balance heaps so size difference is at most 1
Step 4: Add 9 to min heap, no balancing needed
MaxHeap: [5, 3] ↑top
MinHeap: [8, 9] ↑top
Why: Heaps sizes are now equal
Step 5: Add 1 to max heap, no balancing needed
MaxHeap: [5, 3, 1] ↑top
MinHeap: [8, 9] ↑top
Why: Max heap can be one larger than min heap
Step 6: Add 6 to min heap, no balancing needed
MaxHeap: [5, 3, 1] ↑top
MinHeap: [6, 8, 9] ↑top
Why: Heaps are balanced with sizes equal
Result:
MaxHeap: [5, 3, 1] ↑top
MinHeap: [6, 8, 9] ↑top
Median after each insertion: 5, 4, 5, 6.5, 5, 5.5
Annotated Code
DSA Javascript
class Heap {
  constructor(compare) {
    this.data = [];
    this.compare = compare;
  }

  size() {
    return this.data.length;
  }

  peek() {
    return this.data[0];
  }

  push(val) {
    this.data.push(val);
    this._heapifyUp();
  }

  pop() {
    if (this.size() === 0) return null;
    const top = this.data[0];
    const end = this.data.pop();
    if (this.size() > 0) {
      this.data[0] = end;
      this._heapifyDown();
    }
    return top;
  }

  _heapifyUp() {
    let idx = this.size() - 1;
    while (idx > 0) {
      let parent = Math.floor((idx - 1) / 2);
      if (this.compare(this.data[idx], this.data[parent])) {
        [this.data[idx], this.data[parent]] = [this.data[parent], this.data[idx]];
        idx = parent;
      } else {
        break;
      }
    }
  }

  _heapifyDown() {
    let idx = 0;
    const length = this.size();
    while (true) {
      let left = 2 * idx + 1;
      let right = 2 * idx + 2;
      let swap = idx;

      if (left < length && this.compare(this.data[left], this.data[swap])) {
        swap = left;
      }
      if (right < length && this.compare(this.data[right], this.data[swap])) {
        swap = right;
      }
      if (swap === idx) break;
      [this.data[idx], this.data[swap]] = [this.data[swap], this.data[idx]];
      idx = swap;
    }
  }
}

class MedianFinder {
  constructor() {
    this.maxHeap = new Heap((a, b) => a > b); // smaller half
    this.minHeap = new Heap((a, b) => a < b); // larger half
  }

  addNum(num) {
    if (this.maxHeap.size() === 0 || num <= this.maxHeap.peek()) {
      this.maxHeap.push(num); // add to smaller half
    } else {
      this.minHeap.push(num); // add to larger half
    }

    // Balance heaps sizes
    if (this.maxHeap.size() > this.minHeap.size() + 1) {
      this.minHeap.push(this.maxHeap.pop());
    } else if (this.minHeap.size() > this.maxHeap.size()) {
      this.maxHeap.push(this.minHeap.pop());
    }
  }

  findMedian() {
    if (this.maxHeap.size() > this.minHeap.size()) {
      return this.maxHeap.peek();
    } else {
      return (this.maxHeap.peek() + this.minHeap.peek()) / 2;
    }
  }
}

// Driver code
const mf = new MedianFinder();
const stream = [5, 3, 8, 9, 1, 6];
for (const num of stream) {
  mf.addNum(num);
  console.log(mf.findMedian());
}
if (this.maxHeap.size() === 0 || num <= this.maxHeap.peek()) {
decide which heap to add the new number to based on comparison with max of smaller half
this.maxHeap.push(num);
add number to max heap (smaller half)
this.minHeap.push(num);
add number to min heap (larger half)
if (this.maxHeap.size() > this.minHeap.size() + 1) {
check if max heap is too big and rebalance
this.minHeap.push(this.maxHeap.pop());
move top from max heap to min heap to balance sizes
else if (this.minHeap.size() > this.maxHeap.size()) {
check if min heap is bigger and rebalance
this.maxHeap.push(this.minHeap.pop());
move top from min heap to max heap to balance sizes
if (this.maxHeap.size() > this.minHeap.size()) {
if max heap bigger, median is top of max heap
return (this.maxHeap.peek() + this.minHeap.peek()) / 2;
if equal size, median is average of tops
OutputSuccess
5 4 5 6.5 5 5.5
Complexity Analysis
Time: O(log n) per insertion because each addNum operation inserts into a heap and balances heaps which takes log n time
Space: O(n) because all numbers are stored in the two heaps
vs Alternative: Compared to sorting all numbers each time (O(n log n)), this approach is faster for streaming data as it keeps partial order in heaps
Edge Cases
empty stream
findMedian returns undefined or error if called before any number is added
DSA Javascript
if (this.maxHeap.size() === 0 || num <= this.maxHeap.peek()) {
all numbers equal
median is that number, heaps balance correctly
DSA Javascript
if (this.maxHeap.size() > this.minHeap.size() + 1) {
stream with one number
median is that single number
DSA Javascript
if (this.maxHeap.size() > this.minHeap.size()) {
When to Use This Pattern
When you need to find the median of a growing list of numbers efficiently, use two heaps to keep track of smaller and larger halves for quick median retrieval.
Common Mistakes
Mistake: Not balancing the heaps after insertion causing incorrect median calculation
Fix: Always rebalance heaps so their sizes differ by at most one after each insertion
Mistake: Adding all numbers to one heap only
Fix: Distribute numbers between max heap and min heap based on comparison with current median
Summary
Keeps track of median in a stream by using two heaps to split numbers into smaller and larger halves.
Use when you want to find median quickly after each new number in a stream.
The key is balancing two heaps so median is always at the top of one or average of tops.

Practice

(1/5)
1. What is the main purpose of using two heaps (a max-heap and a min-heap) when finding the median of a data stream?
easy
A. To sort all numbers in ascending order
B. To efficiently keep track of the lower half and upper half of the numbers separately
C. To store all numbers in a single data structure
D. To find the maximum number in the data stream

Solution

  1. Step 1: Understand the role of two heaps

    The max-heap stores the smaller half of the numbers, and the min-heap stores the larger half.
  2. Step 2: Explain how this helps find the median

    By balancing these heaps, the median is either the top of one heap or the average of the tops of both heaps.
  3. Final Answer:

    To efficiently keep track of the lower half and upper half of the numbers separately -> Option B
  4. Quick Check:

    Two heaps split data halves = A [OK]
Hint: Two heaps split data halves for median [OK]
Common Mistakes:
  • Thinking heaps sort all numbers fully
  • Using only one heap for median
  • Confusing max-heap and min-heap roles
2. Which of the following is the correct way to insert a number into the two heaps when maintaining the median in JavaScript?
easy
A. Always insert into min-heap
B. Always insert into max-heap
C. Insert into max-heap if number is less than or equal to max-heap root; else insert into min-heap
D. Insert randomly into either heap

Solution

  1. Step 1: Decide heap based on number value

    If the number is smaller or equal to the max-heap root, it belongs to the lower half, so insert into max-heap.
  2. Step 2: Otherwise insert into min-heap

    If the number is greater, it belongs to the upper half, so insert into min-heap.
  3. Final Answer:

    Insert into max-heap if number is less than or equal to max-heap root; else insert into min-heap -> Option C
  4. Quick Check:

    Compare with max-heap root to choose heap [OK]
Hint: Compare with max-heap root to choose heap [OK]
Common Mistakes:
  • Inserting all numbers into one heap
  • Not comparing with max-heap root
  • Inserting randomly without condition
3. Consider the following JavaScript code snippet using two heaps to find the median after inserting numbers 5, 15, 1, 3:
const maxHeap = new MaxHeap();
const minHeap = new MinHeap();

function addNum(num) {
  if (maxHeap.size() === 0 || num <= maxHeap.peek()) {
    maxHeap.insert(num);
  } else {
    minHeap.insert(num);
  }

  if (maxHeap.size() > minHeap.size() + 1) {
    minHeap.insert(maxHeap.extract());
  } else if (minHeap.size() > maxHeap.size()) {
    maxHeap.insert(minHeap.extract());
  }
}

addNum(5);
addNum(15);
addNum(1);
addNum(3);

const median = maxHeap.size() > minHeap.size() ? maxHeap.peek() : (maxHeap.peek() + minHeap.peek()) / 2;
console.log(median);
What is the output printed?
medium
A. 4
B. 3
C. 5
D. 6

Solution

  1. Step 1: Insert numbers and balance heaps

    Insert 5 into maxHeap (empty). Insert 15 into minHeap (15 > 5). Insert 1 into maxHeap (1 <= 5). Insert 3 into maxHeap (3 <= 5). Now maxHeap has 3 elements, minHeap has 1.
  2. Step 2: Balance heaps sizes

    maxHeap size (3) > minHeap size (1) + 1, so move maxHeap.extract() (which is 5) to minHeap. Now maxHeap has [3,1], minHeap has [5,15]. Median is average of maxHeap.peek()=3 and minHeap.peek()=5, so (3+5)/2 = 4.
  3. Final Answer:

    4 -> Option A
  4. Quick Check:

    Median after balancing = 4 [OK]
Hint: Balance heaps sizes, median is middle or average [OK]
Common Mistakes:
  • Not balancing heaps after insertion
  • Taking median as maxHeap.peek() only
  • Incorrect heap extraction order
4. The following code snippet is intended to maintain the median using two heaps, but it has a bug:
function addNum(num) {
  if (num < maxHeap.peek()) {
    maxHeap.insert(num);
  } else {
    minHeap.insert(num);
  }

  if (maxHeap.size() > minHeap.size() + 1) {
    minHeap.insert(maxHeap.extract());
  } else if (minHeap.size() > maxHeap.size()) {
    maxHeap.insert(minHeap.extract());
  }
}
What is the bug in this code?
medium
A. It inserts all numbers into minHeap
B. It uses > instead of >= in the size comparison
C. It balances heaps incorrectly by moving elements the wrong way
D. It does not handle the case when maxHeap is empty before peek()

Solution

  1. Step 1: Identify peek() usage problem

    The code calls maxHeap.peek() without checking if maxHeap is empty, causing an error on first insertion.
  2. Step 2: Explain impact of missing empty check

    Without checking, calling peek() on empty maxHeap throws an error, so the first number cannot be inserted properly.
  3. Final Answer:

    It does not handle the case when maxHeap is empty before peek() -> Option D
  4. Quick Check:

    Empty maxHeap peek() causes error [OK]
Hint: Check heap empty before peek() to avoid errors [OK]
Common Mistakes:
  • Ignoring empty heap before peek()
  • Assuming heaps always balanced before insert
  • Incorrect size comparison operators
5. You are given a stream of numbers: [2, 4, 7, 1, 5, 3]. Using two heaps (max-heap for lower half, min-heap for upper half), what is the median after inserting all numbers? Also, explain why balancing the heaps is important in this process.
hard
A. 3.5; Balancing keeps heaps sizes equal or off by one to find median correctly
B. 4; Balancing is not necessary if we always insert correctly
C. 3; Balancing helps to sort the entire stream
D. 5; Balancing is used to find max value quickly

Solution

  1. Step 1: Insert numbers and balance heaps

    Insert 2 (maxHeap empty, goes to maxHeap). Insert 4 (4 > 2, minHeap). Insert 7 (7 > 2, minHeap). Now minHeap has 2 elements, maxHeap 1, so move minHeap.extract() (4) to maxHeap. Insert 1 (1 <= 4, maxHeap). Insert 5 (5 > 4, minHeap). Insert 3 (3 <= 4, maxHeap). Heaps are balanced with maxHeap size 4 and minHeap size 2, move maxHeap.extract() (4) to minHeap to balance sizes.
  2. Step 2: Calculate median and explain balancing

    Now maxHeap has [3,2,1] with peek()=3, minHeap has [4,5,7] with peek()=4. Since sizes equal (3 and 3), median is (3 + 4)/2 = 3.5. Balancing keeps heaps sizes equal or off by one to find median correctly from the tops.
  3. Final Answer:

    3.5; Balancing keeps heaps sizes equal or off by one to find median correctly -> Option A
  4. Quick Check:

    Balanced heaps give median 3.5 [OK]
Hint: Balance heaps sizes to get median from tops [OK]
Common Mistakes:
  • Not balancing heaps after each insertion
  • Calculating median from unsorted data
  • Confusing median with max or min values
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard