What problem this actually solves
A huge family of array problems boils down to: "for every element, find the nearest element to its left/right that is bigger (or smaller) than it." The brute-force answer is a nested loop — for each index, walk outward until you find a match — which is O(n²) and gets rejected instantly against any n <= 10^5 constraint. The Monotonic Stack pattern answers the exact same question in a single O(n) pass by keeping a stack that is always sorted (strictly increasing or strictly decreasing from bottom to top), discarding elements the moment they become permanently irrelevant.
This is one of the highest-leverage patterns on the whole roadmap: once you recognize it, an intimidating-looking problem collapses into ~10 lines of code you can write from memory.
Recognizing it in a problem statement
Look for phrasing built around relative order among positions, not arbitrary range queries:
- "Next greater element", "next smaller element", "previous greater/smaller"
- "How many days until a warmer temperature" (a distance-to-next-greater question)
- "Span" of a value (how far back does a run of "not bigger than me" extend)
- "Largest rectangle" / "histogram" (each bar needs its left/right boundary — the nearest shorter bar on each side)
- "Trapping water" between bars
- Greedy digit/character removal to build the smallest/largest possible result ("remove k digits", "remove duplicate letters")
- "How many cars merge into one fleet" — anything where a later element can only be "blocked" by, or "absorb", an unresolved earlier one
If you catch yourself wanting to write for i: for j in range(i+1, n): ... to compare every pair, stop and ask whether the comparisons only ever care about the nearest qualifying neighbor. If so, a monotonic stack almost certainly applies.
The template
The stack holds indices, not values (more on why below). At each step, before pushing the current index, pop off everything that the current element invalidates:
def monotonic_stack_template(nums):
n = len(nums)
result = [-1] * n # or 0, or whatever "no answer" means
stack = [] # holds indices, kept monotonic
for i in range(n):
while stack and CONDITION(nums[stack[-1]], nums[i]):
j = stack.pop()
# nums[i] is the answer for index j — resolve it now
result[j] = i # or nums[i], or i - j, depending on the problem
stack.append(i)
# anything left on the stack has no answer to its right
return resultEverything about the pattern is captured by choosing CONDITION and what you do at resolution time.
Increasing vs. decreasing: pick the right invariant
| Stack invariant | Pop condition (before push) | What a pop resolves | Classic use case |
|---|---|---|---|
| Monotonic decreasing (top = smallest so far) | stack top < current | Next greater element for the popped index | Daily Temperatures, Next Greater Element |
| Monotonic increasing (top = largest so far) | stack top > current | Next smaller element for the popped index | Largest Rectangle in Histogram (right boundary) |
The mirror-image versions — scanning right to left, or popping with <=/>= instead of </> — give you previous greater/smaller instead of next, and control how ties are broken (whether equal elements count as "greater"). Get in the habit of explicitly stating, out loud, which of the four variants (next/previous × greater/smaller) you need before writing code — this is exactly the kind of precision a Senior-level interviewer is listening for.
Why this is O(n) despite the nested-looking while loop
This is the complexity-analysis trap interviewers love to probe (see Interview Foundations). The while loop looks like it could make the whole thing O(n²), the same way a naive reading of any nested loop would. But look at what the loop actually does: it only ever pops elements off the stack, and every index is pushed onto the stack exactly once (in the single for loop). Since an index can be popped at most once — after it's popped it's gone for good — the total number of pop operations across the entire run of the algorithm is bounded by n. So total work is: n pushes + at most n pops = O(n) amortized, even though any single iteration of the outer loop could, in the worst case, trigger a long chain of pops (e.g., a strictly decreasing input followed by one huge value). This is the same "amortized analysis" idea as dynamic array resizing — locally expensive, globally cheap.
Values vs. indices — the detail that trips people up
Push indices onto the stack, not values, unless you're certain you'll never need position information. Reasons this matters:
- Distance-based answers (Daily Temperatures wants
i - j, not a value) are impossible to compute if you only stored values. - Duplicate values are ambiguous — if two elements have the same value, only the index tells you which one you're resolving.
- You often need
nums[stack[-1]]for comparisons andstack[-1]itself for the final answer — storing the index gives you both (nums[index]is a cheap lookup).
Common variations
- Circular arrays (Next Greater Element II): conceptually run the array twice by iterating
2ntimes and indexing withi % n. Only compute answers during the first pass over each index; keep pushing on the second pass so later (wrapped-around) elements can still resolve earlier ones. - Span / distance aggregation (Online Stock Span, Car Fleet): instead of storing just an index, store a
(value, aggregate)pair, and when you pop, fold the popped aggregate into the new one before pushing. - Contribution / counting technique (Sum of Subarray Minimums): for each element, find both its previous smaller and next smaller (or smaller-or-equal, to break ties consistently) boundary via two monotonic-stack passes, then the number of subarrays where that element is the minimum is
(i - left) * (right - i). This "count how many windows this element dominates" idea generalizes to a lot of "sum over all subarrays" problems. - Histogram / interval expansion (Largest Rectangle in Histogram): use a monotonic increasing stack of indices; when you pop index
jbecause a shorter bar arrived, the rectangle at heightheights[j]spans from just after the new stack top to just before the current index — widthi - stack[-1] - 1(oriif the stack is empty). Appending a sentinel0to the end of the array lets you flush the whole stack without a second loop. - Greedy digit/character removal (Remove K Digits, Remove Duplicate Letters): the stack holds the "result so far"; you pop trailing characters that are worse than the incoming one (as long as you're still allowed to remove more), which greedily builds the smallest/largest valid sequence.
Common pitfalls and interview gotchas
- Off-by-one on width calculations. In histogram-style problems, whether the width is
i - stack[-1] - 1ori - stack[-1]is the single most common submission bug — draw a small example and count indices explicitly rather than guessing. - Strict vs. non-strict comparisons.
<vs<=in the pop condition changes how duplicate values are handled and can silently produce wrong answers only on inputs with repeats — always ask "what happens with ties?" before finalizing your condition. - Forgetting the leftover stack. After the main loop, indices still on the stack have no valid "next" element — make sure your initialization (
-1,0, etc.) correctly represents "no answer" rather than leaving stale data. - Storing values instead of indices when you'll later need positions or need to distinguish duplicate values (see above).
- Confusing this with a plain stack problem. If the problem doesn't have a "nearest qualifying neighbor" flavor — if it's about nesting or matching instead (balanced parentheses, expression evaluation) — you want the techniques in Stack Simulation, Parsing & Expression Evaluation, not a monotonic invariant.
- Assuming it only works on numeric arrays. Anything with a well-defined order (heights, prices, temperatures, even lexicographic character order) qualifies.
Worked example: teaching the mechanics without spoiling a listed problem
The problem below — previous greater element — uses the identical template you'll apply to every problem in this subtopic, just with the scan direction and comparison flipped relative to "next greater":
def previous_greater_elements(nums):
"""
For each index i, find the value of the nearest element to its
LEFT that is strictly greater than nums[i]. -1 if none exists.
Monotonic DECREASING stack, scanned left to right.
"""
n = len(nums)
result = [-1] * n
stack = [] # indices, values strictly decreasing bottom -> top
for i in range(n):
# Anything <= nums[i] can never be the "previous greater" for
# anyone after i either, so it's safe to discard permanently.
while stack and nums[stack[-1]] <= nums[i]:
stack.pop()
if stack:
result[i] = nums[stack[-1]]
stack.append(i)
return result
# [10, 4, 2, 20, 40, 12]
# -> [-1, 10, 4, -1, -1, 40]Notice the structure is identical to the generic template: pop while the invariant is violated, read off an answer (this time from the new top of stack, not the popped element — because "previous greater" resolves at push time, not pop time), then push. Once this shape is automatic, adapting it to "next greater," "span," or "histogram width" is a small, mechanical change to the condition and the resolution step — practice noticing which of the two ("resolve on pop" vs. "resolve on push after popping") a given problem needs, since mixing them up is a common source of bugs under interview pressure.
Further Resources (Optional)
- GeeksforGeeks — Introduction to Monotonic StackArticle15m
- Labuladong — Monotonic Stack (English)Article15m
- Tech Interview Handbook — Stack cheatsheetArticle10m
- NeetCode — Daily Temperatures (Monotonic Stack walkthrough)Video12m
- CP-Algorithms — Minimum Stack / Minimum QueueArticle20m
- Codeforces — The Monotonic Stack Tree: O(log n) range queries via binary liftingArticle25m
- VisuAlgo — Stack, Queue & Deque interactive visualizationReference15m
- take U forward — Next Greater Element, Two VariantsVideo20m
Practice Problems
Interview relevancy:!!!Critical — must-have!!Important — highly recommended!Good to have~Niche — rarely asked
- Next Greater Element IEasy!!!1/515m
- Daily TemperaturesMedium!!!2/520m
- Next Greater Element IIMedium!3/525m
- Online Stock SpanMedium!3/525m
- Car FleetMedium!!3/530m
- Remove K DigitsMedium!!3/525m
- Sum of Subarray MinimumsMedium!!4/535m
- Largest Rectangle in HistogramHard!!!5/550m
- Maximal RectangleHard!!5/545m
Optional Practice (Extra Reps)
For once you've cleared the main set above and want more reps on this pattern. These don't count toward the roadmap's progress stats — solve them purely for your own benefit.
- Nearest Smaller ValuesCSES~2/520m
- 132 PatternMedium!3/530m
- Remove Duplicate LettersMedium!4/530m