Monotonic Stack & Deque

Keep a stack (or deque) whose values stay sorted. When the incoming element violates that order, pop — and every pop resolves an answer: a next greater/smaller element, a bounding wall for a rectangle, a merged car fleet, a window maximum. Because each element is pushed and popped at most once, the whole sweep is O(n) amortized. The same idea reaches across Stack, Two Pointers, and Sliding Window problems.

Technique7 problems
The unlock

Keep the stack sorted. The moment a new element would break that order, the elements it breaks are exactly the ones it answers — so pop them, record their result, and push the newcomer to wait for its own. One sorted pile, one pop per resolved element, O(n) total.

MENTAL MODEL A sorted pile of elements still waiting for an answer

Picture a stack where every element is still waiting for something — the next taller bar, a shorter wall, a faster car. You keep the pile sorted (decreasing or increasing), so the only element that can ever be resolved next is sitting right on top. When a new element arrives that violates the order, it is precisely the answer for the elements it violates: pop each one, hand it its result, and stop when the order holds again.

That single discipline unifies problems that look unrelated:

  • Next greater / smaller.A taller value pops every shorter one still waiting — each pop's answer is the newcomer.
  • Histogram rectangle.A shorter bar is the right wall for every taller bar on the stack — each pop fixes one bar's rectangle.
  • Car fleet. A car that arrives no later than the one ahead merges into it — the pop collapses two fleets into one.
  • Window max (deque).The same sorted pile, but pruned from both ends so the front is always the current window's maximum.
The reframe →don't ask “what should I store?” Ask “what is each element waiting for, and does the newcomer resolveit?” If yes, the newcomer pops it and you've got a monotonic stack.

SEE IT A taller element pops everyone it towers over

Brute force re-reads the right side for every element — the same cells over and over:

Brute force: "next greater to the right"  →  for each i, scan right
    nums = [ 2  1  3 ]

    i=0 (2):  look right → 1? no … → 3? yes  ◄ rescanned 1 then 3
    i=1 (1):  look right → 3? yes            ◄ rescanned 3 again
    i=2 (3):  look right → (nothing)

                            ~n²/2 comparisons, the same cells re-read.

The waste IS re-reading the right side for every element.

The monotonic stack keeps only the elements still waiting, in sorted order. When 3 arrives on [2, 1, 3] it cascades — popping the 1 then the 2, each pop resolving one waiting element:

nums = [ 2, 1, 3 ]    stack holds indices WAITING for a bigger value
                      (values strictly DECREASING bottom→top)

i=0  val 2   stack empty, push           stack(vals): [ 2 ]
i=1  val 1   1 < 2, push                 stack(vals): [ 2, 1 ]
i=2  val 3   3 > 1 → pop 1, ans[1] = 3   stack(vals): [ 2 ]
             3 > 2 → pop 2, ans[0] = 3   stack(vals): [ ]
             push 3                       stack(vals): [ 3 ]

leftover: 3 has nobody bigger to its right → ans[2] = -1
result:  [ 3, 3, -1 ]

The arriving 3 "beats" everything it towers over and pops them.
Each popped element just found its next-greater answer — and each
index is pushed once, popped once → O(n) despite the inner loop.
Why it's O(n), not O(n²) → the inner while looks nested, but each index is pushed once and popped once across the whole run. Total pops ≤ total pushes = n. The nesting is an illusion.

SEE IT The deque is the same pile, pruned from both ends

For a sliding window maximum the sorted pile needs one extra move: the current max can age out of the window before any bigger value beats it, so you also drop stale indices off the front. That two-ended pruning is the only difference between a monotonic stack and a monotonic deque:

Sliding-window max, k = 3:   nums = [ 1  3  -1  -3  5 ]
                                  idx     0  1   2   3  4

 deque stores INDICES; values decrease front→back; FRONT = window max.

 r=0  push 0          dq(vals): [ 1 ]
 r=1  3 ≥ 1 pop back, push 1   dq(vals): [ 3 ]          ← 1 was dominated
 r=2  -1 < 3 push 2  dq(vals): [ 3, -1 ]   window full → max = 3
 r=3  -3 push 3      dq(vals): [ 3, -1, -3 ] win [3,-1,-3] → max = 3
 r=4  front idx 1 ≤ 4-3 → shift; 5 pops -3,-1,3, push 4
                      dq(vals): [ 5 ]       win [-1,-3,5] → max = 5

 result: [ 3, 3, 5 ]

 Stack pops from ONE end. A deque also drops STALE indices off the
 FRONT, because the max can age out of the window before it's beaten.
The smell test → if you need the extreme of a fixed window (not the whole prefix), reach for the deque — pop dominated values off the back like a stack, and expire the front when the window moves past it.

HOW TO THINK The cold-start ladder — run this on any new problem

When you suspect a monotonic structure, climb these rungs in order:

  1. Am I asking for the nearest greater/smaller, a span, or a window extreme?“next warmer day”, “largest rectangle”, “max of every window” — all monotonic.
  2. What is each element waiting for?Say it in one sentence. If you can finish “each element waits for ___ and a newcomer that ___ resolves it”, the push/pop logic is decided.
  3. Pick the order by the answer the popped element gets. Pop when the newcomer is bigger → popped elements learn their next-greater (decreasing stack). Pop when smaller → next-smaller (increasing stack).
  4. Stack or deque? Whole-prefix query → stack (one end). Fixed-window extreme → deque (expire the front too).
  5. Push the index, not the value. You almost always need i − j distance or a span width on a pop; recover the value with nums[index].
  6. Handle the leftovers. Whatever survives the loop never got resolved: its answer is the default (-1), or a sentinel flushes it (histogram's trailing 0).
The one question that picks the direction →“when I pop someone, what did they just learn?” Say the answer out loud and the comparison sign follows.

SAY IT Name what the stack is waiting for

Before coding, say in one sentence what each item on the stack is waiting for. Finish that sentence and the logic writes itself:

  • Daily Temperatures:“each day waits for a warmer day; a warmer day pops it and records the distance.”
  • Largest Rectangle:“each bar waits for a shorter bar; a shorter bar pops it and fixes its right edge.”
  • Car Fleet:“each fleet waits to see if a car behind catches up; a catching car pops (merges) into it.”
  • Sliding Window Maximum:“each index waits to be the max; a bigger newcomer pops it from the back, and the window's left edge expires it from the front.”
The invariant → a monotonic stack is always sorted. The pop loop exists precisely to restorethat order before each push. If your stack isn't sorted after a push, your comparison sign is wrong.

LOOP SHAPE Two skeletons in disguise — stack and deque

Strip away the problem and every monotonic-stack solution is the same three moves: pop everyone the newcomer beats (they just found their answer) → record it → push the newcomer to wait for its own:

for each index i, left to right:

    while stack not empty AND nums[i] beats nums[stack.top]:
        j = stack.pop()        # j was WAITING — i is its answer
        record answer for j     # a value, a distance i - j, or an area

    push i                     # i now waits for ITS own answer

# anything still on the stack never found an answer → default (-1)

The monotonic-deque adds one rung — expire the front when the window slides past it — so the front is always the live window extreme:

for each index right, left to right:

    if front index has aged out of the window:
        pop it off the FRONT

    while back value is dominated by nums[right]:
        pop it off the BACK     # it can never be the max again

    push right onto the back

    once the window is full:
        emit nums[front]        # the running max/min

The onlythings that change between problems are the “beats” comparison (> vs <, strict vs non-strict for duplicates) and what you record on a pop (a value, a distance, an area). The shape never changes.

MNEMONIC Pop everything you tower over.

Pop everything you tower over. A monotonic stack holds elements still waiting for an answer, kept in sorted order; when a newcomer violates the order it pops — and each pop resolves one element. The Visualize tab steps the next-greater cascade so you can watch the pops fire.

PATTERN A sorted stack that resolves on violation

A monotonic stack maintains its elements in sorted order (strictly increasing or decreasing). You scan once; for each new element you pop everything that violates the order, and every pop is a resolved answer. Then you push the newcomer so it waits for its resolution. The four classic shapes:

  • Next greater / smaller.The popped element's answer is the newcomer (Daily Temperatures, Next Greater Element).
  • Span / area bounded by walls. The newcomer is the right wall; the new stack top after popping is the left wall (Largest Rectangle, Maximal Rectangle, Trapping Rain Water).
  • Merge by domination. A newcomer that catches up absorbs the element ahead (Car Fleet collapses cars into fleets).
  • Window extreme (deque). Prune from both ends so the front is the running max/min of a fixed window (Sliding Window Maximum).

KEY IDEA Each pop is one answer — that is the whole trick

The reason this pattern is so powerful is that a pop and an answer are the same event. You never search for an element's neighbor; you wait, and when the neighbor arrives it pops you. The stack is a queue of unresolved obligations, kept sorted so the next resolvable one is always on top.

Invariant → bottom-to-top the stack is monotonically decreasing for next-greater problems and increasing for next-smaller / span problems. The instant a push would break the invariant, every popped element has found its answer.

Push the index, not the value — that way a pop can compute a distance (i − j) or a span width, and you recover the value with nums[index].

COST Amortized O(n) — why the nested loop is not O(n²)

Brute-force scan
O(n²)
For each element, rescan to find its neighbor / wall.
Monotonic stack
O(n)
Each element pushed once, popped at most once.

The inner while loop can pop at most n elements across the entire outer loop — total pops are bounded by total pushes, which is exactly n. The deque variant is identical: every index enters and leaves each end at most once.

VARIANT Stack vs deque — one end or two

  • Monotonic stack (one end). All pushes and pops happen at the top. Use it for whole-prefix queries: next greater/smaller, histogram spans, car fleets. An element leaves only when a newcomer resolves it.
  • Monotonic deque (two ends). Pop dominated values off the back like a stack, and expire stale indices off the front when the window slides past them. Use it for the extreme of a fixed-size window, where the max can age out before it is beaten.

The mental model is the same sorted pile; the deque just adds front-eviction so a bounded window never reports a stale extreme.

RUN IT Pop everything you tower over

step 0 / 23
STARTFor each value, find the next greater value to its right. The stack holds indices still waiting for an answer; its values stay strictly decreasing. Pop everything you tower over.
1function nextGreater(nums: number[]): number[] {
2 const n = nums.length;
3 const res = new Array<number>(n).fill(-1);
4 const stack: number[] = []; // indices; values decreasing top→bottom
5
6 for (let i = 0; i < n; i++) {
7 // current value beats everything it towers over → resolve them
8 while (stack.length && nums[i] > nums[stack[stack.length - 1]]) {
9 const j = stack.pop()!; // j was WAITING; i is its answer
10 res[j] = nums[i];
11 }
12 stack.push(i); // i now waits for ITS next-greater
13 }
14 return res; // leftover indices keep -1
15}
73
0
74
1
75
2
71
3
69
4
72
5
76
6
73
7
stack (indices, values ↓ top)
empty
next-greater answers
·
·
·
·
·
·
·
·
current elementon the stack (waiting)just resolved (popped)answered earlier
slowfast

TRIGGERS When you see ___ → reach for ___

Reach for a monotonic structure when you need the nearest element that is greater or smaller, a span bounded by walls, or the extreme of a moving window. The tell is that each element is “waiting” for some future element that resolves it — and that future element arrives in scan order.

"next greater / next smaller element", "next warmer day"monotonic stack of indices; pop when the newcomer violates the order, that pop is the answer
"previous smaller / nearest smaller to the left"monotonic increasing stack; the answer is the new top after popping (or scan right-to-left)
"largest rectangle / maximum span bounded by taller (or shorter) bars"monotonic stack of indices; the popped bar’s width = i − new-top − 1
"trapping rain water" — water bounded by walls on both sidesmonotonic decreasing stack of indices; each pop is a horizontal layer of trapped water
"merge / collapse by who catches up" (car fleet, asteroid collision)sort or scan in arrival order; a dominating newcomer pops (absorbs) the element ahead
"max / min of every window of size k"monotonic deque of indices; pop dominated off the back, expire stale off the front, front = extreme

RED FLAGSWhen it's NOT this pattern

  • You need a global min/max that keeps changing arbitrarily. A heap (priority queue) serves that; a monotonic structure only answers directional (nearest) or windowed extremes.
  • The window is variable-size, not fixed. For a sliding window with a constraint (longest/shortest valid), use the plain two-cursor window; the monotonic deque is for the extreme of a window, usually fixed width.
  • The “next greater” is in a 2-D grid or graph. The monotonic stack is linear; 2-D extensions need a row-by-row reduction (Maximal Rectangle) or a different sweep.
  • You need every pair, not the nearest one.If the answer compares non-adjacent or all-pairs relationships, the “each pop is one answer” accounting breaks — reach for sorting, prefix sums, or DP.

TEMPLATE Next greater element — decreasing stack

When → The problem asks for the next (or previous) element that is greater, or the distance to it. Keep a decreasing stack of indices; pop while the newcomer exceeds the top, recording each popped element's answer.

next-greater-element-decreasing-stack.ts
// Monotonic DECREASING stack → next greater element to the right, in O(n).
// The stack holds INDICES whose values decrease bottom→top: each one is
// "waiting" for a value bigger than it to show up on its right.
function nextGreater(nums: number[]): number[] {
  const n = nums.length;
  const res = new Array<number>(n).fill(-1);
  const stack: number[] = [];                 // indices, values decreasing top→bottom

  for (let i = 0; i < n; i++) {
    // Every value the newcomer towers over has just found its answer → pop them.
    while (stack.length && nums[i] > nums[stack[stack.length - 1]]) {
      const j = stack.pop()!;                  // j was waiting; i is its next-greater
      res[j] = nums[i];                        // record the ANSWER (or i - j for distance)
    }
    stack.push(i);                             // push the INDEX, not the value
  }
  return res;                                  // leftover indices have no greater → stay -1
}
Push the index, not the value → Daily Temperatures returns i − stack.pop() (a distance), which is impossible if the stack only holds values. Recover the value with nums[index] when comparing.

TEMPLATE Histogram largest rectangle — span stack

When → You compute a max area or span bounded by a shorter element on either side (Largest Rectangle, Maximal Rectangle per row). Append a sentinel 0 so the final loop iteration flushes every remaining bar.

histogram-largest-rectangle-span-stack.ts
// Largest rectangle in a histogram — monotonic INCREASING stack of bar indices.
// When a shorter bar arrives it is the right wall for every taller bar still on
// the stack; each pop fixes that bar's full max-width rectangle.
function largestRectangle(heights: number[]): number {
  const bars = [...heights, 0];                // sentinel 0 flushes every remaining bar
  const stack: number[] = [];                  // increasing stack of indices
  let best = 0;

  for (let i = 0; i < bars.length; i++) {
    while (stack.length && bars[i] < bars[stack[stack.length - 1]]) {
      const h = bars[stack.pop()!];            // the popped bar's height
      // Left wall = the new top (exclusive); right wall = i (exclusive).
      const w = stack.length ? i - stack[stack.length - 1] - 1 : i;
      best = Math.max(best, h * w);
    }
    stack.push(i);
  }
  return best;
}
Width on a pop → when you pop index j, the left wall is the new stack top (exclusive) and the right wall is i (exclusive), so w = i − newTop − 1. If the stack is empty after the pop, j is the shortest bar so far and w = i.

TEMPLATE Sliding window maximum — monotonic deque

When → You need the maximum (or minimum) of every fixed-size window in O(1) per step. A decreasing deque of indices keeps the front as the window max; expire stale front indices and pop dominated back values before each push.

sliding-window-maximum-monotonic-deque.ts
// Maximum of every window of size k — monotonic DECREASING deque of indices.
// The deque is pruned from BOTH ends: the front drops stale (out-of-window)
// indices; the back drops values the newcomer dominates. Front is always the max.
function slidingWindowMaximum(nums: number[], k: number): number[] {
  const res: number[] = [];
  const dq: number[] = [];                     // indices; nums[dq] decreasing front→back

  for (let right = 0; right < nums.length; right++) {
    // 1. Evict the front if it has slid out of the window.
    if (dq.length && dq[0] <= right - k) dq.shift();

    // 2. Drop dominated values off the BACK before pushing the newcomer.
    while (dq.length && nums[dq[dq.length - 1]] <= nums[right]) dq.pop();

    dq.push(right);

    // 3. Once the first full window is formed, the front index is the max.
    if (right >= k - 1) res.push(nums[dq[0]]);
  }
  return res;
}
Prune from both ends → the front check (dq[0] ≤ right − k) is what makes it a deque rather than a stack — the max can leave the window before any larger value beats it. Store indices so you can detect that expiry.

PITFALL Storing values instead of indices

Most monotonic problems need the index on the stack/deque, not the value. On a pop you typically want a distance (i − j), a span width, or — for the deque — to test whether the front has slid out of the window. Storing only the value makes all three impossible. Recover the value with nums[index] during comparison.

PITFALL Strict vs non-strict comparison — handling duplicates

Whether you use > vs >= (and < vs <=) in the “pop while” condition decides what happens with equal elements. >= pops duplicates eagerly so only the rightmost duplicate records an answer; > keeps duplicates so each answers independently. In Sliding Window Maximum, popping equal values off the back (<=) is fine because the later index is the one that stays in the window longer. Read “strictly greater” vs “greater or equal” in the statement carefully.

PITFALL Forgetting to drain the stack at the end

After the main loop, elements still on a monotonic stack have no resolving element — their answer is the default (-1, or 0 for some problems). Histogram-style code sidesteps this by appending a sentinel that forces a final flush; without it, the tallest bars never get their rectangle. A common bug is returning before draining.

PITFALL Forgetting the deque expires from both ends

A monotonic deque is not just a stack — you must also drop indices that have aged out of the window off the front (dq[0] ≤ right − k). Omit that and the front can report a maximum that is no longer inside the window. Pop dominated values off the back, expire stale indices off the front, then read the front.

PROBLEMS

#739Daily TemperaturesThe canonical decreasing stack.Drills “each pop is one answer”: keep a decreasing stack of day indices; when a warmer day i arrives, pop every cooler day j and set answer[j] = i − j. This is the move every other problem here generalizes.#84Largest Rectangle in HistogramThe span / wall move.A shorter bar is the right wall for every taller bar on the stack; each pop fixes that bar's rectangle with width = i − newTop − 1. Append a sentinel 0 to drain the stack cleanly.#85Maximal RectangleHistogram, applied per row. Reduce the 2-D grid to a heights array per row (running column counts of 1s), then run the largest-rectangle stack on each row. Drills reusing the span move inside an outer sweep.#853Car FleetThe merge-by-domination move. Sort cars by start position descending and walk a stack of arrival times: a car that arrives no later than the fleet ahead is popped (it merges in). The remaining stack height is the fleet count — the pop is the merge.#32Longest Valid ParenthesesStack of indices as boundary markers. Push -1 as a base, push ( indices, and on ) pop then measure i − stack.top. Drills using the stack top as the left boundary of the current valid run — the same “new top is the wall” idea as the histogram.#42Trapping Rain WaterCross-category reach: a Two Pointers problem. The monotonic-stack solution keeps a decreasing stack of indices; when a taller bar arrives, each pop is a horizontal layer of water bounded by the new top (left wall) and the newcomer (right wall). Shows the same wall-bounding logic outside the Stack category (the two-pointer solution is the other route).#239Sliding Window MaximumCross-category reach: a Sliding Window problem, solved with the deque variant. Pop dominated values off the back, expire indices that slid past the window off the front, and the front index is the window max. This is where the technique steps beyond the single-ended stack into a two-ended deque.