skip to content

Deque, lazy-deletion heap, or per-window recompute: how do you pick a rolling-maximum implementation?

level: principalimportance: should knowfreq 38%

answer

  1. start from the measured numbers
  2. how large is the window, really?
  3. memory bounded by window or by backlog?
  4. who edits this code in a year?
  5. name the trigger for switching

basics

~20 s

Pick by window size, stream volume and maintenance cost, not by asymptotics alone. Per-window recompute is fine when the window is tiny, a lazy-deletion heap is simple but grows with the backlog, and the deque's linear total with window-bounded memory pays off at high volume.

solid answer

~60 s

Start from the numbers, not the exponents. Recomputing each window costs `O(n*k)`; with a window of five samples on a dashboard refreshing once a second that is nothing, and it is the code least likely to be broken by the next person to touch it. A heap of candidate entries with lazy expiry costs a logarithmic factor per push and pop, is easy to reason about, and generalises if the requirement grows to a top-three list or to out-of-order updates — but its memory tracks the backlog rather than the window unless expired entries are actively purged. The monotonic deque is linear in total with memory bounded by the window, and that is the one that survives a batch path over hundreds of millions of readings with a wide window. I ship the simplest option that meets the measured budget, and if that is the deque, I pay for it with a stated invariant, a property test against the naive version as an oracle, and a named trigger for revisiting the choice.

go deeper

for a junior

Know the three costs: rescanning is the window size times the stream length, a heap is logarithmic per reading, and the monotonic structure is linear overall with window-bounded memory.

for a middle

Explain why a heap needs lazy or handle-based deletion here at all, and what each variant costs in memory as well as in time.

for a senior

Argue from measured numbers on the real workload and name the operational failure of each option: heap memory growth, rescan latency at a wide window, and silent breakage of the deque's invariant under edits.

for a principal

Own the tradeoff between the fastest structure and the one your team can maintain, state the window size or volume that triggers a switch, and say how you would migrate safely using the naive version as an oracle.

## The three candidates, priced honestly | approach | time over n readings | memory | notes | |---|---|---|---| | rescan each window | `O(n*k)` | `O(1)` extra | trivial to read; no invariant to break | | heap with lazy expiry | `O(n log n)` worst case | up to `O(n)` entries | each entry pushed once, popped once; stale entries linger until they surface | | heap keeping exactly the window | `O(n log k)` | `O(k)` | needs deletion by handle, so entries must be tracked | | monotonic deque | `O(n)` total | `O(k)` | amortized; one step may cost `O(k)` | Two details in that table are where candidates go wrong. First, the lazy-expiry heap is *not* `O(n log k)`: it does not remove an entry when the window passes it by, only when that entry reaches the top and is found stale, so the heap can accumulate entries for readings long since departed and its size drifts toward the length of the stream. Getting to `O(n log k)` requires a heap that supports deleting a specific entry, which means carrying handles. Second, the deque's `O(n)` is amortized: the total is linear, but a rising run followed by a spike makes one step proportional to the window. ## What actually decides it **How big is the window, really?** Asymptotics are statements about growth, not about your numbers. At a window of five samples the rescan does five comparisons per reading — comfortably cheaper than the heap's pointer chasing, and competitive with the deque's bookkeeping. The linear structure only pulls decisively ahead when the window is large: at a window of ten thousand over a hundred million readings, the rescan is a trillion comparisons and simply not an option. **Is the stream bounded?** On a long-running collector the lazy heap's memory profile is the operational risk, because it grows with the backlog rather than the window. The deque's memory is bounded by the window by construction, which is the property that matters when the process must run for weeks without attention. **Is the requirement stable?** The deque encodes one specific fact: for a *single* extreme over a *forward-advancing* window, an older smaller reading is permanently irrelevant. Ask for the top three per window, or for readings that can be retracted or corrected after arrival, and the dominance argument no longer applies — a heap or an ordered structure absorbs that change, the deque does not. Knowing which requirements are on the roadmap is a legitimate input to the choice. **Is the guarantee the right shape?** Linear total with an occasional window-proportional step is a throughput guarantee. A batch pipeline absorbs it happily. A path with a hard per-sample deadline needs the tail bounded, and that argues for the per-operation bound of a heap, or for chunking the work. **Who maintains it?** This is the input engineers most often leave out, and at a lead level it is the one you are expected to raise. The deque is short but its correctness lives in an invariant that is invisible to a reader who does not already know it: two ends, two eviction rules, positions rather than values. A change made in a hurry — expiring by value, using a strict comparison where a non-strict one was intended, dropping the report guard — produces plausible output that is subtly wrong, and nothing crashes. If you choose it, buy down that risk deliberately: a comment stating the invariant on the eviction loop, and a randomised property test that checks the fast version against the naive rescan on sequences full of duplicates, flat stretches, and windows of one and of the full length. ## How to present the decision A strong answer sequences the reasoning rather than announcing a winner: measure the current numbers, name the budget the feature must meet, choose the simplest implementation that meets it, and state the trigger — a window size, a sample rate, a memory ceiling — at which you would move to the next one. Keeping the naive version alive as a test oracle is what makes that later migration cheap, because the replacement can be validated against it rather than against a hand-written expectation. The weak answer is "linear beats logarithmic, so the deque": correct arithmetic, absent engineering, and it is exactly the reflex the question is designed to catch.

  • What makes a heap with lazy expiry risky on an unbounded stream of readings?
    Stale entries are only discarded when they surface at the top, so the heap grows toward the length of the stream rather than the size of the window. Memory becomes a function of the backlog, which on a long-running collector is the failure that gets someone paged at three in the morning.
  • If the requirement changes to the top three loads in each window, does the deque still work?
    Not directly. Dominance is an argument about a single winner, and the second-largest reading of a future window may already have been evicted. You would keep several mirrored structures or move to a heap or ordered structure — and that is the moment to revisit the whole choice rather than bolt onto the deque.
  • How do you make the linear version safe for a team to own?
    State the invariant in a comment on the eviction loop, keep the naive rescan as a test oracle, and drive both with randomised sequences including duplicates, flat stretches and windows of one and of the full length. The property test is what turns a clever structure into a maintainable one.
  • When would you refuse the linear structure even though it is asymptotically best?
    When the measured budget is already met, the window is tiny, and the surrounding code is owned by people who will need to change it. A quadratic loop everyone can read beats a linear one nobody dares touch — until the numbers say otherwise, which is why the switching trigger should be written down.

saying these in an interview costs you the question

  • Picks the linear structure purely because linear beats logarithmic
  • Ignores that a lazy heap grows with the backlog, not the window
  • Assumes asymptotics decide the case at a window of five
  • Treats maintainability as outside the engineering tradeoff
  • Claims a lazy-expiry heap keeps only window-sized memory

context