An array of daily temperatures needs, for every day, the number of days until a warmer one. Comparing each day against all later days is O(n^2): most of that work re-scans stretches already known to be colder. The redundancy has structure. Once a warmer day arrives, every earlier colder day pending an answer is resolved at once and never consulted again.

A monotonic stack captures exactly that. It holds indices whose values stay sorted — increasing or decreasing — by popping any element that can no longer be an answer before the next element is pushed. Scanning left to right for the next greater value, the stack keeps values decreasing from bottom to top; the arriving element pops every smaller index below it, and it is their next greater element. Each index is pushed once and popped once, so the nested-looking “pop while” is linear in total.

A monotonic deque generalises this to a moving window. It stores window candidates in sorted order at the back and evicts from the front when the window slides past an index, which is why indices — not values — must be stored: eviction is keyed on position. This recovers O(n) sliding-window maximum, the query a plain Sliding Window cannot answer because a maximum has no inverse to subtract on removal.

Core shape: ordered scan → pop violators before each push → each element enters and leaves once → O(n) nearest-greater/smaller spans and window extrema.

Visualization pending

Planned StepTrace: a monotonic-stack card showing a stack kept monotonic — before pushing an element, all elements that violate the order are popped, so each element enters and leaves once. No matching renderer exists in engine.js yet.

Why the pops are free

The stack answers “next greater element” for every index in one pass. Scanning left to right it holds indices whose values decrease from bottom to top. Before pushing index i, every index whose value is less than a[i] is popped, because a[i] is the next greater element each of them was waiting for. Index i is then pushed. Indices still on the stack when the scan ends have no greater element to their right.

The invariant is that the stack, read bottom to top, is a decreasing sequence of values whose positions increase — the still-unanswered candidates in the order they must be resolved. A popped index is settled forever: the element that popped it is strictly closer and strictly greater than anything further right could offer as a next neighbour. This is what makes the same scan solve the descendants — largest rectangle in a histogram (previous-smaller and next-smaller boundaries), trapping rain water (a basin closed when a taller bar pops the stack) — without re-examining settled indices.

The deque keeps the same monotone contents but adds a second exit. Values decrease from front to back; the front is always the current window maximum. Each new index i drops the front if it has slid out of the window, pops from the back every index with value <= a[i] (they can never again be the max while a newer, larger a[i] is present), then pushes i. Storing indices is what makes the front eviction possible — the deque must know when a candidate leaves the window, which only its position records.

The cost argument is a charging scheme. Each index is pushed exactly once and popped at most once across the entire scan, so the inner “pop while” runs at most n times in total, not per outer step. Charging each pop to the unique element that performed it bounds the whole run at O(n) — the same amortised accounting behind Union-Find and dynamic-array growth.

Complexity

CaseTimeAuxiliary spaceCause
Any input, monotonic stackO(n) amortizedO(n)Each index is pushed once and popped at most once; total pops <= n, so the inner loop is O(n) summed over the whole scan, not per step.
Any input, monotonic dequeO(n) amortizedO(k)Same push-once/pop-once accounting; the front eviction (dq.First <= i - k) keeps every stored index inside (i - k, i], so the deque holds at most k elements regardless of input.
Brute-force next-greaterO(n^2)O(1)For each index, a fresh forward scan re-reads stretches already known to be smaller; no state carries between indices.

The O(n) bound is amortized, not per-operation: a single push can trigger a run of pops, but those pops are elements charged only once each. Stack space is the peak occupancy — a decreasing input for a decreasing stack, e.g. [5, 4, 3, 2, 1], satisfies the pop condition on no push (no element has a next-greater to its right), so every index stays resident and gives the O(n) worst case. The deque is capped at k by front eviction and never reaches O(n).

When the invariant is set wrong

The monotone direction must match the query. A decreasing stack yields the next greater element; flip the pop comparison and it yields the next smaller. Choosing the direction backwards produces a plausible, fully populated result array that answers the opposite question — nothing crashes, and no-duplicate test inputs may even look right.

Strict versus non-strict comparison decides ties. < and <= in the pop condition differ only when equal values meet: one treats a duplicate as still a live candidate, the other as superseded. On histogram widths or first-versus-last-occurrence problems this flips the answer, and because both variants pass inputs without adjacent duplicates, the discrepancy stays hidden until equal neighbours appear.

The deque must store indices, not values. A window maximum is a distance-aware query: the front is evicted precisely when its stored index falls out of range. A deque of raw values has discarded the positions, so it cannot tell when a candidate has left the window and returns a stale maximum from a slot no longer in scope.

Sliding-window maximum is the exact case a scalar Sliding Window aggregate fails. A running sum is reversible — subtract the departing element and the invariant holds in O(1). A maximum has no inverse: when the current max leaves the window, the next-largest is unknown without rescanning. The monotonic deque restores O(n) by keeping every “still possibly maximal” index instead of a single collapsed scalar, so the answer survives a removal.

Reference drawer

Comparison

ApproachTimeSpaceRequired inputStronger caseWeaker case
Monotonic stackO(n) amortizedO(n)Single left-to-right (or right-to-left) passNext/previous greater-or-smaller for every index; histogram, rain waterQueries that are not a nearest-neighbour comparison
Brute-force next-greaterO(n^2)O(1)NoneTiny n, one-off checksAny input past a few hundred elements
Monotonic dequeO(n) amortizedO(k)Fixed window size, indices storedSliding-window max/min over a streamOrder statistics beyond the extremum (median, k-th)
Heap over the windowO(n log k)O(k)Comparable keysWindow median or k-th order statistic alongside the maxPure min/max, where the deque’s O(n) wins
Sparse table / Segment TreeO(n log n) build, O(1)/O(log n) queryO(n log n) / O(n)Static array (sparse table); mutable range (segment tree)Arbitrary range max on unchanging or updatable dataA single left-to-right pass where preprocessing never pays back

A monotonic stack is the O(n) tool for next-greater and next-smaller spans, paying O(n) space to keep unanswered candidates live; brute force is preferable only when n is trivially small. A monotonic deque is the O(n) sliding-window max/min that beats a Heap’s O(n log k) — the heap earns its log factor only when the window needs an order statistic the deque cannot expose. A sparse table or Segment Tree answers arbitrary ranges rather than a moving window, and its O(n log n) build pays back only under repeated ad-hoc range queries.

Questions

References