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.
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
- 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.
Why Monotonic Stacks Beat Nested Loops Every Time
When you reach for a nested loop to find the next greater element, you're writing O(n²) code that will fail at scale. A monotonic stack collapses that to O(n) by exploiting a simple invariant: each element enters and leaves the stack exactly once.
The insight isn't the stack itself — it's that you can discard elements the moment they become irrelevant. In the Next Greater Element problem, if you're processing left to right and element X has already found its greater element, keeping X around only slows you down. Pop it. Move on.
This pattern repeats across dozens of LeetCode problems because the underlying geometry is identical: you're searching for the first element that breaks some monotonic property. The stack just tracks which candidates are still alive. When the breaker arrives, the stack tells you exactly whose answer just got resolved.
Stop thinking of it as a data structure problem. It's an elimination problem. The stack is just your death tracker.
Tuning the Monotonic Direction to Match Your Query
Choosing increasing vs decreasing isn't random — it depends entirely on what you're looking for. If you need the next larger element, you want a decreasing stack (largest at bottom, smallest at top). When a bigger element arrives, everything smaller on top pops and gets its answer.
Flip it for next smaller element. An increasing stack (smallest at bottom) ensures that when a smaller element appears, the larger ones above finally get their resolution.
The most common mistake? Building the wrong polarity and wondering why nothing works. Trace through one pass manually. If the stack never pops, you've inverted the comparison operator.
Largest Rectangle in Histogram exploits this with a twist: you don't just pop on smaller heights — you need both the next smaller element to the left and right. Two passes, or one pass with index tracking. Same stack, just smarter bookkeeping.
Get the direction right first. Everything else is index arithmetic.
Reading Stack State: What the Indices Really Tell You
Most tutorials show you elements. Production code needs indices. When you store indices instead of values, you recover both the value (via array lookup) and the distance between elements — critical for subarray problems.
Consider the sum of minimums of all subarrays problem. You can't just find the next smaller element; you need to know how many subarrays each element dominates. That requires left span and right span — both derived from index differences at pop time.
The stack's internal order also carries meaning. During processing, elements still on the stack haven't found their next greater element yet. They're the unlucky ones — descending peaks that nothing bigger has overtaken. In stock trading terms, they're the positions still underwater.
When you understand that the stack is storing unresolved futures, the whole pattern clicks. You're not just pushing and popping. You're resolving debts. Each pop is a liability settled. Each final leftover is a default.
That mental model makes the monotonic stack intuitive, not mechanical.
The Sliding Window Monotonic Stack: Max/Min of Every K-Sized Subarray
Most devs think monotonic stacks only handle "next greater element" queries. That's a rookie mistake. The real power shows up when you pair a monotonic stack with a sliding window — you get O(n) solutions for problems that scream for a deque but don't need one.
The trick: instead of pushing every element blindly, you maintain a strictly decreasing stack for max queries (or increasing for min). Before pushing a new element, pop from the back until the stack is monotonic again. Then trim expired indices from the front. The first element in the stack is always your window's answer. No heap, no nested loops, no bullshit.
This pattern kills problems like "Sliding Window Maximum" where naive solutions hit O(n*k). Production streaming pipelines and real-time analytics use this exact technique to maintain running statistics on unbounded data. Understand the indices, and you own the window.
Offline Queries: Precomputing the Monotonic State for Reuse
Stop running the same monotonic stack scan five times for five different queries. If your problem gives you a static array and asks multiple questions about "next greater" or "previous smaller," build the monotonic stack once and reuse the precomputed mappings.
Here's the senior approach: first pass builds a HashMap<Integer, Integer> for nextGreater given each index. Second pass builds previousSmaller. Now any query — "what's the next greater element after index 4?" — is O(1) lookups. You pay O(n) upfront and laugh at nested loops later.
This is exactly how database query optimizers work: they precompute index structures once and reuse them across query plans. Stop recomputing state. Build once, query many. The monotonic stack is your ETL pipeline — run it once and cache the results. Your future self will send you a beer.
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.
Print stack after each iteration: print(f'i={i}, stack={[nums[idx] for idx in stack]}')Print result array: print(f'result={result}')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
20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.
That's Stack & Queue. Mark it forged?
7 min read · try the examples if you haven't