Monotonic Stack — Equal-Price Operator Off-by-One
Using > instead of >= in monotonic stack while loop causes stock span off by 1 for equal prices.
- Monotonic stack = stack with monotonic order enforced on push
- O(n) time because each element pushed/popped at most once
- Stack stores indices — not values — to calculate distances
- Decreasing stack delivers next greater; increasing gives next smaller
- Test with duplicates to catch strict vs non-strict bugs early
A monotonic stack is a regular stack with one constraint: elements are always in strictly increasing or decreasing order. When pushing a new element would break that order, you first pop everything that violates it. This simple rule makes it the optimal solution to a whole family of 'next greater/smaller element' problems — all in O(n) instead of O(n²).
The monotonic stack is one of the most underrated patterns in algorithm interviews. I've watched engineers spend 45 minutes brute-forcing problems in O(n²) when the monotonic stack gives O(n) in 15 minutes of clean code. Once you internalise the pattern, you'll spot it in problems that don't obviously look like a stack problem — and that recognition is what separates strong algorithm interviewers from average ones. Don't underestimate it: get the direction wrong and you're debugging for half an hour.
What Is a Monotonic Stack?
A monotonic stack is a stack that maintains its elements in monotonically increasing or decreasing order. The invariant is enforced during push: before pushing a new element, pop all elements that violate the desired order. This guarantees that at any point, the stack is sorted.
The key insight: each element is pushed once and popped at most once, giving linear time regardless of how many elements get popped in a single step.
Monotonic stacks excel at problems that ask for the next or previous greater/smaller element — especially when you need to compute distances, spans, or areas based on those relationships.
Production news: you'll find this pattern in stock market analysis, weather data processing, and even in some database query optimisers. It's not just LeetCode theory.
- Each element enters the stack when it's pushed.
- It waits until a new element 'beats' it (greater for decreasing stack, smaller for increasing).
- When beaten, it pops and records its answer.
- Elements that never get beaten have no answer (return -1).
Monotonic Decreasing Stack — Next Greater Element
The classic problem: given an array, for each element find the next element to its right that is strictly greater. Brute force is O(n²). Monotonic stack is O(n) — each element is pushed and popped at most once.
The invariant: the stack always holds indices of elements in decreasing order of value. When we see a new element larger than the stack top, that top has found its 'next greater' answer.
Here's a Python implementation, plus a Java version to show language independence.
Deque<Integer> (ArrayDeque) and use peekLast()/pollLast() for stack operations. The algorithm is identical.Monotonic Increasing Stack — Next Smaller Element and Largest Rectangle
Flip the comparison direction and you get the monotonic increasing stack — elements in the stack are always in increasing order. Pop when the current element is smaller than the top. Use for 'next smaller element' problems.
The Largest Rectangle in Histogram is the canonical hard problem that uses this pattern.
Key insight: when you pop a bar, you know its right boundary (current index) and its left boundary (the new stack top after pop). The width is the difference. This gives O(n) instead of O(n²).
Variations and Nuances
- Trapping Rain Water: Use decreasing stack to find left and right boundaries for each trough.
- Sum of Subarray Minimums: Use increasing stack to find contribution of each element as minimum.
- Previous Greater Element: Same decreasing stack, iterate left-to-right and record answer for popped elements (the previous greater is the current stack top after popping).
Common nuance: strict vs non-strict comparisons. For example, 'next greater element' typically uses strict >, but 'next greater or equal' requires >=. Misunderstanding this shifts the output by one position.
Here's the rain water snippet — notice it's the same decreasing stack pattern with index arithmetic.
Implementation Pitfalls and Debugging
Even with the algorithm correct, monotonic stack implementations have common bugs: 1. Storing values instead of indices — you need indices to compute distances, spans, and widths. 2. Off-by-one in sentinel — sentinel should be at the end (not beginning), and its value must be small enough (0 for increasing stack, large for decreasing). 3. Incorrect update of start index in Largest Rectangle — when popping, the new bar's start must be set to the popped bar's start to extend width.
Debugging these often starts with printing the stack at each iteration and verifying invariants. Use print(f'stack: {stack}') with indices, and trace with a tiny array like [2,1,2].
Here's a debug trace for the NGE example:
Real-World Applications: Beyond LeetCode
- Financial data: Stock span analysis, calculate rolling max/min windows for candlestick charts.
- Database query optimisers: Finding next smaller/larger values in sorted data for partition pruning.
- Graphics: Determining visible line segments by comparing slopes (monotonic stack of line equations).
- Image processing: Computing local minima/maxima for edge detection.
In each case, the pattern is the same: iterate a sequence, maintain a monotonic structure, and use the pop event to compute something about the relationship between elements.
Think of it as a 'delayed answer' pattern — elements wait until a later element determines their fate.
- The answer is always an element that comes later (or earlier if you reverse iteration).
- The stack preserves the order of 'waiting' elements.
- The invariant ensures that when an answer arrives, it correctly resolves all waiting elements that it beats.
Stock Span Calculation Failure Due to Incorrect Operator
price > stack[-1][1] instead of price >= stack[-1][1]. Equal prices were causing the stack to not pop, so the span didn't extend back.while stack and price > stack[-1][1]: to while stack and price >= stack[-1][1]:. Re-ran pipeline with correct results.- Always identify whether the problem requires strict or non-strict comparison.
- Test with duplicate elements to catch this bug early.
- Write a short comment next to the while condition explaining the logic.
- Span calculations are sensitive to operator choice — verify with regression tests.
Key takeaways
Common mistakes to avoid
5 patternsStoring values instead of indices in the stack
Forgetting sentinel in Largest Rectangle in Histogram
Using wrong strictness for equal elements
Confusing increasing vs decreasing stack direction
Not processing remaining elements after loop (no sentinel, no manual drain)
Interview Questions on This Topic
Implement Next Greater Element using a monotonic stack.
Frequently Asked Questions
That's Stack & Queue. Mark it forged?
3 min read · try the examples if you haven't