Monotonic Stack Pattern Fixes O(n²) Stock Indexing
30-sec timeout from O(n²) loops on 10k ticks.
20+ years shipping performance-critical code where algorithms decide the bill. Everything here is grounded in real deployments.
- Declarative structure: maintain sorted order in a stack by popping violations on push
- Two variants: decreasing (next greater) and increasing (next smaller)
- Each element pushed and popped at most once → O(n) amortised
- Real‑world: stock span APIs, histogram area, trapped rainwater
- Production trap: wrong comparison sign (<= vs <) silently shifts answers
Imagine you're standing in a line at a concert, and everyone wants to know: 'Who's the next taller person behind me?' Instead of every person turning around and scanning the whole crowd (slow!), you use a bouncer who maintains a running list — kicking out anyone shorter as taller people arrive. That bouncer's list is your monotonic stack. It's always sorted, always current, and always answers your question in one pass.
Most developers hit a wall with array problems that demand knowing the 'next greater' or 'previous smaller' element at every index. The naive approach — a nested loop — works on paper but collapses at scale. When you're processing stock prices, rendering bar charts, or evaluating weather forecasts over millions of data points, O(n²) isn't a trade-off; it's a dealbreaker. The monotonic stack pattern is one of those rare tools that turns a quadratic problem into a linear one without any exotic data structure magic — just a regular stack used with discipline.
The core problem it solves is deceptively simple: for each element in an array, find the nearest element to its left or right that is greater or smaller than it. Brute force checks every pair. The monotonic stack makes one observation that changes everything — if you're scanning left-to-right and you encounter an element that 'dominates' the element on top of the stack, the dominated element has found its answer. You process it, pop it, and never look at it again. That amortized O(1) pop-per-element is where the O(n) total time comes from.
By the end of this article you'll be able to implement both monotonically increasing and decreasing stacks from scratch, identify which variant a problem requires, handle tricky edge cases like duplicate values and circular arrays, and explain the amortized time complexity to an interviewer without flinching. You'll also walk away with a mental checklist that maps problem phrasing directly to the right stack orientation — so you stop guessing.
How a Stack Becomes Monotonic and Why That Cuts O(n²) to O(n)
A monotonic stack is a stack whose elements remain in strictly increasing or decreasing order as you push new items. The core mechanic: before pushing a new element, pop all elements that violate the desired order. This single pass over the input, with each element pushed and popped at most once, yields O(n) total time — a direct replacement for nested loops that would cost O(n²).
In practice, you maintain either a monotonic increasing stack (for next smaller element) or monotonic decreasing stack (for next greater element). The key property: when you pop an element, you know the current incoming element is the first element that breaks the monotonic condition for the popped element. This gives you an immediate answer for that popped element's "next" relationship without revisiting it. The stack holds indices, not values, so you can compute distances or retrieve values from the original array.
Use this pattern whenever you need to find the next (or previous) greater or smaller element for every position in an array — stock span, daily temperatures, histogram largest rectangle, or sliding window maximum. In real systems, this replaces O(n²) brute-force indexing with a single linear pass, which matters when processing millions of price ticks or sensor readings per second.
How Monotonic Stack Works — Step by Step
Let's build a monotonic decreasing stack to find the next greater element (NGE) for each index. The stack maintains indices of elements in decreasing order of their values (biggest on top? No — actually, decreasing stack means values decrease from bottom to top). Here's the algorithm:
- Initialize an empty stack.
- For each index i from 0 to n-1:
- - While stack is not empty AND
arr[stack.top] < arr[i]: - pop top index j → record
result[j] = arr[i] - - Push i onto stack.
- Any indices still in stack have no rightward greater elements → their result stays -1.
For an increasing stack (next smaller element), flip the condition to arr[stack.top] > arr[i].
Key insight: the popped element's answer is the current element. This is a one‑pass algorithm.
< when you need <=.Worked Example — Tracing the Algorithm
Find next greater element for [4, 5, 2, 10, 8].
i=0 (4): stack empty. Push 0. stack=[0]. i=1 (5): arr[0]=4 < 5. Pop 0. result[0]=5. Stack empty. Push 1. stack=[1]. i=2 (2): arr[1]=5 >= 2. Push 2. stack=[1,2]. i=3 (10): arr[2]=2 < 10. Pop 2. result[2]=10. arr[1]=5 < 10. Pop 1. result[1]=10. Stack empty. Push 3. stack=[3]. i=4 (8): arr[3]=10 >= 8. Push 4. stack=[3,4]. End: stack has [3,4] remaining. result[3]=-1, result[4]=-1.
Result: [5, 10, 10, -1, -1]
stack=[index of 10, index of 8] at the end.<= instead of <.Implementation — Python and Java
Below are clean implementations in Python and Java. The pattern is identical across languages: a stack of indices, a while loop with the monotonic condition, and a final sweep for unpopped indices.
Important: always store indices in the stack, not values. You need the index to fill the result array. The value is retrieved via arr[stack.top].
Deque<Integer> instead of Stack (deprecated).ArrayDeque is faster and not synchronized.ArrayIndexOutOfBounds when accessing arr[stack.peek()] on empty stack.Deque in Java, list in Python.while loop per element structure.Real‑World Use Cases — Beyond the Basics
The monotonic stack isn't just an interview trick. It appears in production code for:
- Largest Rectangle in Histogram: For each bar, the area is
height * (right_smaller - left_smaller - 1). Monotonic increasing stack finds both boundaries in one pass. - Trapping Rain Water: Maintain stack of decreasing heights; when a taller bar arrives, compute water trapped above the popped bar.
- Stock Span Problem: For each day, find how many consecutive days before today the price was ≤ today's. Monotonic decreasing stack with indices.
Each of these uses the same core pattern with a small twist.
Complexity Analysis — Why O(n) Amortised
Each element is pushed onto the stack exactly once. Each element can be popped at most once. Total number of push/pop operations is therefore ≤ 2n. The inner while loop may run many times for a single outer iteration, but across the entire run, the total pops equal the number of pushes. This gives an amortised O(1) per element. Space is O(n) for the stack in the worst case (e.g., decreasing array for next greater).
Formally: each iteration of the while loop pops an element, and each element is popped at most once → O(n) total while iterations.
What A Monotonic Stack Actually Is — And Isn't
A monotonic stack is just a stack that enforces a single rule: every new element must maintain the stack's sorted order before you push it. If that means popping elements that break the order, you pop. That's it. There's no magic. No hidden complexity.
Why does this matter? Because that forced ordering lets you answer one question instantly: "What was the last element that was larger (or smaller) than the current one?" That's the root of every nearest-greater/smaller-element problem. When you pop an element, you know exactly which incoming element dominated it. That relationship is the key to collapsing O(n²) brute-force scans into O(n) amortised time.
Most tutorials fluff this up with diagrams and multiple examples. Here's the raw truth: monotonic stacks are a specific tool for a narrow set of problems — finding the next/previous greater/smaller element. If you don't need that relationship, don't touch this pattern.
How To Spot A Monotonic Stack Problem Instantly
Stop memorising problem titles. Look for the pattern in the language. If the problem says "nearest element that is greater/smaller to the left/right" — that's your signal. No other technique gives you that relationship in O(n).
But here's where juniors get lost: the problem won't always say "nearest greater element". It'll say "stock span" or "trapping rain water" or "largest rectangle in histogram". Those are camouflage. The core mechanic is still: for each element, find the first element to its left or right that's larger or smaller. Learn to see through the disguise.
Three questions to ask yourself in any array problem: 1. Does the answer for each element depend on the first larger/smaller neighbour? 2. Can I process elements left-to-right or right-to-left in a single pass? 3. Does the naive O(n²) solution involve checking each element against every neighbour?
If you answered yes to at least two of those, a monotonic stack is your weapon.
Conclusion
The monotonic stack pattern fundamentally reshapes how you approach subarray and subsequence problems by trading excess comparison work for a single-pass, ordered stack. Instead of checking every element against every other element in O(n²) time, the monotonic property ensures that each element enters and leaves the stack exactly once, achieving O(n) amortised runtime. This efficiency comes from encoding dependency relationships — next greater element, nearest smaller value, or maximum width of a histogram — into stack invariants. The pattern is not magic; it is just a disciplined application of stack discipline combined with a monotonic invariant. When you see a problem asking for the next larger element, the maximum area under a histogram, or the longest valid subarray with a monotonic constraint, ask yourself: can I derive this answer from a monotonic stack? If yes, implement a while loop that pops while the invariant breaks, then push the current index. The hardest part is deciding whether the stack should increase or decrease, and that decision hinges on whether you need the next greater or next smaller element. Once you internalise that, the monotonic stack becomes one of the most reliable tools in your DSA toolkit.
What A Monotonic Stack Actually Is — And Isn't
A monotonic stack is a stack whose elements are arranged in either strictly increasing or strictly decreasing order from bottom to top. It is not a separate data structure — it is a disciplined usage pattern of a standard stack. When you push a new element, you first pop all elements that violate the monotonic order, then push. The key insight is that the stack never holds elements out of order; it enforces a sorted traversal of indices, not values. This is not a priority queue, because the order is based on value magnitude, not priority; and it is not a sorted array, because the stack only holds a subset of elements at any time. When people misuse the term, they often confuse it with a monotonic queue (used for sliding window maximum) which dequeues from both ends. The monotonic stack is strictly LIFO with a value constraint. Its power comes from the fact that when an element is popped, you know it is the first time a violating element appears to its right (or left, depending on traversal direction). That immediate contextual knowledge is what enables O(n) solutions for problems like largest rectangle in histogram or trapping rain water — it is not a catch-all for every subarray problem, only those where the answer depends on the nearest violating neighbour.
Stock Price Indexing Slowdown
for loop inside a for loop was 'simple and correct'.- Always profile before assuming O(n²) is acceptable for variable input volume.
- Monotonic stack is a drop‑in replacement for many 'next greater/smaller' problems — it trades memory for massive time savings.
- Test with production data volumes in staging, not just with a handful of examples.
arr[stack.top] < arr[i]; using <= treats equal values as not greater and may skip valid answers.< or >) or non‑strict (<= or >=). For Next Greater Element with duplicates, using <= gives the strictly greater — that's usually correct.`print(f'index {i}: stack = {stack}')` inside the loop`assert len(result) == len(arr)` after the loopKey takeaways
Common mistakes to avoid
3 patternsUsing the wrong comparison sign (<= vs <)
< for strict, <= for greater‑or‑equal.Forgetting to process remaining elements in the stack at the end
Storing values instead of indices in the stack
i (the index), not arr[i]. Retrieve value via arr[stack.peek()] when comparing.Practice These on LeetCode
Interview Questions on This Topic
What is a monotonic stack and when should you use one?
Frequently Asked Questions
20+ years shipping performance-critical code where algorithms decide the bill. Everything here is grounded in real deployments.
That's Stack & Queue. Mark it forged?
8 min read · try the examples if you haven't