The Two Heaps pattern maintains an ordered partition while values arrive. A max-heap named lower stores the lower half, and a min-heap named upper stores the upper half. Their roots meet at the partition boundary, so the median is available without sorting the accumulated stream.

Two invariants make the result correct:

  1. Every value in lower is less than or equal to every value in upper.
  2. lower.Count equals upper.Count or exceeds it by one.

A new value enters lower when it is no greater than the lower root; otherwise it enters upper. Moving one root across restores the size rule. With an odd count, lower.Peek() is the median. With an even count, the median is the average of both roots.

Visualization

The stream rail shows insertion order. After each value arrives, the lower max-heap exposes the largest value from the lower half and the upper min-heap exposes the smallest value from the upper half. Rebalancing moves only a root, preserving both the partition and the size difference of at most one.

Complexity
n
number of values processed so far

Where the Pattern Applies

The pattern fits running medians and other streaming partition problems where both sides of a boundary must remain available. It differs from Top-K Elements: Top-K keeps only k survivors and discards the rest, while Two Heaps retains every value because either half may contribute a future median.

Deletion changes the mechanism. Removing an arbitrary expired value, as in a sliding-window median, is not efficient with only the basic heap API because locating that value is linear. Indexed heaps or lazy-deletion maps add the missing removal path; they are justified only when the window actually expires values.

Questions

References