Monotonic Stack: Pattern, Use Cases and Coding Examples
- Monotonic stack = regular stack + the invariant that elements stay in increasing or decreasing order. Violating elements are popped first.
- Monotonic decreasing stack β next/previous greater element. Monotonic increasing stack β next/previous smaller element.
- Time complexity is O(n) β each element is pushed at most once and popped at most once regardless of the while loop.
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.
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.
from typing import List def next_greater_element(nums: List[int]) -> List[int]: """ For each element, find the next strictly greater element to its right. Returns -1 if none exists. Approach: monotonic decreasing stack of indices. When a new element is greater than the stack top, the top has found its answer. Time: O(n) β each element pushed/popped at most once Space: O(n) β stack + result array """ n = len(nums) result = [-1] * n stack = [] # indices, maintained in decreasing order of nums[index] for i in range(n): # While current element beats the stack top: # the top's 'next greater' is nums[i] while stack and nums[i] > nums[stack[-1]]: idx = stack.pop() result[idx] = nums[i] stack.append(i) # Remaining indices in stack β no greater element β stay -1 return result print(next_greater_element([2, 1, 2, 4, 3])) # [4, 2, 4, -1, -1] # Trace: # i=0: push 0. stack=[0] (val=2) # i=1: 1<2 β push 1. stack=[0,1] # i=2: 2>1 β pop 1, result[1]=2. 2==2 β push 2. stack=[0,2] # i=3: 4>2 β pop 2, result[2]=4. 4>2 β pop 0, result[0]=4. push 3. stack=[3] # i=4: 3<4 β push 4. stack=[3,4] # End: indices 3,4 remain β result[3]=result[4]=-1 def stock_span(prices: List[int]) -> List[int]: """ Stock Span: for each day, how many consecutive previous days (including today) had price <= today's price? Monotonic decreasing stack of (index, price) pairs. Time: O(n), Space: O(n) """ span = [0] * len(prices) stack = [] # (index, price) in decreasing price order for i, price in enumerate(prices): while stack and price >= stack[-1][1]: stack.pop() span[i] = i + 1 if not stack else i - stack[-1][0] stack.append((i, price)) return span print(stock_span([100, 80, 60, 70, 60, 75, 85])) # [1, 1, 1, 2, 1, 4, 6]
[1, 1, 1, 2, 1, 4, 6]
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.
from typing import List def next_smaller_element(nums: List[int]) -> List[int]: """ Monotonic INCREASING stack. For each element, find the next strictly smaller element to its right. Time: O(n), Space: O(n) """ n = len(nums) result = [-1] * n stack = [] # indices in increasing order of nums[index] for i in range(n): while stack and nums[i] < nums[stack[-1]]: idx = stack.pop() result[idx] = nums[i] stack.append(i) return result print(next_smaller_element([4, 2, 3, 1, 5])) # [2, 1, 1, -1, -1] def largest_rectangle_histogram(heights: List[int]) -> int: """ Largest rectangle in histogram. Classic monotonic increasing stack problem. Time: O(n), Space: O(n) Key insight: when we pop a bar because the current bar is shorter, we know the FULL width that popped bar can extend to. """ max_area = 0 stack = [] # (start_index, height) β monotonic increasing by height heights = heights + [0] # sentinel 0 forces all remaining bars to pop for i, h in enumerate(heights): start = i while stack and stack[-1][1] > h: idx, height = stack.pop() width = i - idx max_area = max(max_area, height * width) start = idx # This bar can extend back to where the popped bar started stack.append((start, h)) return max_area print(largest_rectangle_histogram([2, 1, 5, 6, 2, 3])) # 10 (bars of height 5 and 6 form a 2-wide Γ 5-tall = 10 rectangle)
10
| Problem Type | Stack Direction | Pop Condition | Classic Examples |
|---|---|---|---|
| Next Greater Element (right) | Decreasing | current > top | Daily Temperatures, NGE |
| Previous Greater Element (left) | Decreasing | current >= top | Stock Span |
| Next Smaller Element (right) | Increasing | current < top | Next Smaller Element |
| Previous Smaller Element (left) | Increasing | current <= top | Sum of Subarray Minimums |
| Area / width problems | Increasing | current < top | Largest Rectangle, Trapping Rain Water |
π― Key Takeaways
- Monotonic stack = regular stack + the invariant that elements stay in increasing or decreasing order. Violating elements are popped first.
- Monotonic decreasing stack β next/previous greater element. Monotonic increasing stack β next/previous smaller element.
- Time complexity is O(n) β each element is pushed at most once and popped at most once regardless of the while loop.
- Store indices in the stack, not values β you need the index to compute distances, widths, and spans.
- When you pop an element, you know its 'answer' β the element that triggered the pop is the next greater/smaller for the popped element.
β Common Mistakes to Avoid
- βStoring values instead of indices in the stack β almost every problem requires the index to compute span, width, or distance. Always store indices.
- βForgetting the sentinel at the end for problems that require flushing the remaining stack β adding a 0 at the end of histogram heights ensures every bar gets processed.
- βUsing strict vs non-strict comparison incorrectly β 'pop when current > top' (strict) vs 'pop when current >= top' (non-strict) affects how equal elements are handled. Think through equal-element cases explicitly.
Interview Questions on This Topic
- QImplement Next Greater Element using a monotonic stack.
- QWhat is the time complexity of the monotonic stack approach and why?
- QWhen do you use a monotonic increasing vs decreasing stack?
- QSolve Largest Rectangle in Histogram in O(n).
Frequently Asked Questions
What is a monotonic stack?
A monotonic stack is a stack where elements are maintained in strictly increasing or decreasing order. When a new element would violate the order, elements are popped until the invariant is restored. This pattern solves 'next/previous greater/smaller element' problems in O(n) time.
When should I use a monotonic stack?
Use it when the problem asks for the next or previous element that is greater or smaller than the current element. Common triggers: 'next greater temperature', 'stock span', 'largest rectangle', 'trapping rainwater'. If a brute-force solution uses a nested loop to look left or right for a boundary, a monotonic stack likely gives O(n).
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.