Skip to content

Monotonic stack / queue

intermediateCommon atGoogleAmazonMicrosoftUberCitadelRead firstStacksQueues and dequesSliding windowPractise in the Sandbox →

A monotonic stack keeps its values in sorted order by popping anything the new element “beats” before pushing it. The pops are exactly the moments an element meets its next greater (or smaller) neighbour. A monotonic deque does the same but also drops expired items from the front, giving the max or min of every sliding window. Each element is pushed and popped once: O(n) total.

Stand at the end of a street of buildings and look left. You can only see the buildings that are taller than everything between them and you. A short building hidden behind a tall one will never be visible from anywhere further right either. So you can forget it.

That’s the whole insight: once an element is dominated, it’s useless forever. A monotonic stack throws dominated elements away the moment they become useless, so what remains is always sorted, and the useful candidate is always on top.

  • For “next greater to the right”: the stack holds elements still waiting for a bigger value. They’re in decreasing order (a bigger one would have resolved the smaller ones below it). When a new value arrives, it resolves every waiting value smaller than it.
  • For “previous greater to the left”: same scan, but you read the answer from the top after popping.

Brute force asks each element “scan right until you find someone bigger”: O(n²) for a decreasing-then-spike input. The stack does all of those scans in one pass.

The bars are the “buildings”. Yellow ones are waiting on the stack. Watch a tall value arrive and resolve several waiting ones in a burst, then notice the stack values always read in decreasing order.

Next greater element with a decreasing stack
step through
2
7
3
5
4
6
8
1
2
7
3
5
4
6
8
1
01234567
input
·
·
·
·
·
·
·
·
next greater (· = still waiting)
stack[]
stack values[]
pops0
currentwaiting on the stackjust resolvedresolved
The stack holds indices still waiting for a bigger value to their right. Their values only decrease from bottom to top.
1 / 24
The problem says… Why a monotonic structure fits
“next greater / next smaller element”, “how many days until a warmer day” The pop is the answer.
“previous greater / smaller”, “span”, “how far back until…” After popping, the top is the nearest dominating element.
“max / min of every window of size k” A monotonic deque: drop dominated items at the back, expired items at the front.
“largest rectangle”, “sum over all subarrays of their min” Each element’s reach left and right is its previous/next smaller.
a brute force that scans outward from every element until a bigger/smaller one That scan is what the stack shares across elements.
  1. Fill the answer with a default (-1: nobody bigger).
  2. For each index i: while the top’s value is smaller than a[i], pop it and record a[i] as its answer.
  3. Push i. The stack’s values stay decreasing from bottom to top.
  4. Whatever remains at the end never found a bigger value and keeps the default.
next_greater.py
def next_greater(a: list[int]) -> list[int]:
"""For each a[i], the first value to its right that is strictly bigger, or -1."""
ans = [-1] * len(a) # 1. default: nobody bigger shows up
stack: list[int] = [] # 2. indices still waiting; their values decrease bottom -> top
for i, x in enumerate(a):
while stack and a[stack[-1]] < x: # 3. x resolves every smaller waiting index
ans[stack.pop()] = x
stack.append(i) # 4. x now waits for its own answer
return ans
if __name__ == "__main__":
assert next_greater([2, 7, 3, 5, 4, 6, 8, 1]) == [7, 8, 5, 6, 6, 8, -1, -1]
assert next_greater([5, 4, 3]) == [-1, -1, -1]
assert next_greater([1, 1, 2]) == [2, 2, -1] # strictly bigger: equal values keep waiting
assert next_greater([]) == []
print("ok")

Store indices, not values: you need to know where to write the answer, and indices also give distances (i - j) for “how many days until” questions.

Monotonic stack (previous greater, read after popping)

Section titled “Monotonic stack (previous greater, read after popping)”

The same loop, with the answer read from what’s left on the stack.

spans.py
def spans(prices: list[int]) -> list[int]:
"""Span of day i: how many consecutive days ending at i had price <= prices[i]."""
out: list[int] = []
stack: list[int] = [] # 1. indices of a strictly decreasing run of prices (bottom -> top)
for i, p in enumerate(prices):
while stack and prices[stack[-1]] <= p: # 2. smaller-or-equal days are covered by today
stack.pop()
# 3. the top is now the previous strictly greater day (or none)
out.append(i - stack[-1] if stack else i + 1)
stack.append(i)
return out
if __name__ == "__main__":
assert spans([100, 80, 60, 70, 60, 75, 85]) == [1, 1, 1, 2, 1, 4, 6]
assert spans([1, 2, 3]) == [1, 2, 3]
assert spans([3, 3, 3]) == [1, 2, 3]
assert spans([]) == []
print("ok")

A window both gains an element on the right and loses one on the left. A stack can’t lose from the bottom, so use a deque:

  1. Keep a deque of indices whose values decrease from front to back.
  2. Before adding index i, pop from the back every index whose value is ≤ a[i]. They are older and not bigger, so they can never be a window max again.
  3. Push i on the back.
  4. If the front index has slid out of the window (front ≤ i − k), pop it from the front.
  5. Once the window is full, the front is its maximum.

Step through it, and try a large k to see that the deque stays small:

Sliding window maximum with a monotonic deque
step through
1
3
-1
-3
5
3
6
7
2
012345678
·
deque, front → back (indices [])
·
window maxima
window—
deque size0
current windowfront = current maxin deque (a future max)just removed
The deque holds indices that could still become a window max, values decreasing front → back. The front is always the current max.
1 / 25
Time Space
brute force (scan outward from each element) O(n²) worst O(1)
brute force window max O(n·k) O(1)
monotonic stack O(n) O(n) worst (strictly decreasing input)
monotonic deque, window max O(n), independent of k O(k)
heap for window max (lazy deletion) O(n log n) O(n)

The amortized argument: every index is pushed once and popped at most once. The inner while loops, summed over the whole scan, run at most n times. So O(n), even though one step can pop many.

  • Increasing vs decreasing. Next smaller uses an increasing stack (pop while the top is bigger).
  • Right-to-left scan. Scan from the end to get “next greater” by reading the top after popping, like the span example.
  • Both sides at once. When an element is popped by i, then i is its next smaller and the new top is its previous smaller. Largest-rectangle-in-histogram uses exactly this.
  • Circular arrays. Walk the array twice (i % n) so elements near the end can find answers near the start.
  • Window min. Flip the comparison: keep values increasing front to back.
  • Stack of pairs for streams. For an online “span” query, push (value, span) and merge popped spans into the new one, so you don’t need the whole history.
  • Wrong strictness with duplicates. Decide whether “greater” means > or ≥, and match < vs <= in the pop condition.
  • Storing values when you need indices. You can’t write the answer back or measure distance without the index.
  • Forgetting leftovers. Elements still on the stack at the end need the default answer.
  • Using a list as a deque. Python list.pop(0) and JS shift() are O(n), which silently makes the window version O(n·k). Use collections.deque, a head index, or a ring buffer.
  • Evicting by value instead of index in the window deque. Duplicates make value-based eviction remove the wrong copy.
  • Start from the brute force: “For each element I scan right until something bigger. The repeated scans overlap; an element hidden behind a bigger one can never be anyone’s answer, so I can discard it.”
  • Name the invariant: “The stack holds indices of unresolved elements, values decreasing bottom to top.”
  • Give the O(n) amortized argument (push once, pop once).
  • Expect follow-ups: duplicates, circular input, “now the min too”, “now the input is a stream” (the deque works online), “now the window is time-based” (evict by timestamp instead of index).
Check yourself
pick one
In next-greater with a stack, why can a popped index be discarded forever?
Check yourself
pick one
In the window-max deque, a new value 5 arrives and the back of the deque holds a value 5 from an earlier index. Should you pop it?
Check yourself
pick one
With k = n/2, how many deque operations does the window-max algorithm do in total?

Classic textbook exercises, not gym questions:

  • Days until a warmer temperature, for each day.
  • Largest rectangle in a histogram.
  • Trapping rain water (try it with a stack, then with two pointers).
  • Sum of the minimums of all subarrays (each element’s contribution = left reach × right reach).