Monotonic Stack — The Complete Guide
Master the Monotonic Stack pattern from first principles. Learn to recognize 'next greater/smaller' problems instantly, evolve brute-force into O(n) solutions, and confidently solve the hardest stack-based interview questions.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
Imagine you're standing in a line of people with different heights, and you want to know: "For each person, who is the first taller person standing to their right?" The brute-force way is to scan right from every person — that's O(n²). But there's a much smarter way.
Walk through the line from right to left. Keep a stack of people you've seen. For each new person, pop everyone shorter than them off the stack — those shorter people are irrelevant because the current person "blocks" them for anyone further left. The person remaining on top of the stack (if any) is the answer. Then push the current person onto the stack.
The stack always stays in order — either strictly increasing or strictly decreasing from bottom to top. That's why it's called a monotonic stack. The monotonic property is what lets us skip irrelevant elements and solve the problem in O(n) total time, because each element is pushed and popped at most once.
Analogies to Build Intuition
Skyscrapers Blocking the View
You're looking at a city skyline from the right. A tall building blocks all shorter buildings behind it. As you scan left, you only care about buildings that are taller than everything you've seen so far. The stack holds the 'visible' buildings — each new tall building pops all shorter ones it blocks.
Waiting for a Warmer Day
You check the temperature every day and ask: 'How many days until a warmer day?' You don't need to compare every pair of days. You just need to remember the days that are still 'waiting' for their answer. When a hot day arrives, it resolves all the cooler days stacked up before it.
Stacking Books by Size
You're organizing books on a shelf where each book must be shorter than the one below it (monotonically decreasing). When a tall book arrives, you remove all shorter books on top until you find one that's taller, then place the new book. The removed books have found their 'next taller' — it's the book that just arrived.
What kind of problems does it solve?
Monotonic Stack is your go-to when:
- You need the next greater element (or next smaller) for each position
- You need the previous greater element (or previous smaller) for each position
- You need to compute spans — how far back/forward a condition holds
- You need to find the largest rectangle in a histogram or matrix
- You need to compute contribution of each element as min/max of all subarrays
- You want to reduce an O(n²) "for each element, scan left/right" pattern to O(n)
The core insight
A monotonic stack maintains a sorted order by popping elements that violate the monotonic property. Each pop is meaningful — it pairs the popped element with the element that caused the pop. This pairing is the answer to "next greater/smaller" questions. Since each element is pushed once and popped at most once, the total work is O(n).
Pattern Recognition
Recognizing a monotonic stack problem is the hardest part. The problem rarely says "use a stack." Instead, look for these signals.
Keywords & Signals in the Problem Statement
Use Monotonic Stack when you see...
- ✅"Next greater element" or "next smaller element"
- ✅"Previous greater/smaller element"
- ✅"How many days until a warmer temperature"
- ✅"Stock span" — consecutive days with price ≤ today
- ✅"Largest rectangle in histogram"
- ✅"Maximal rectangle in binary matrix"
- ✅"Sum of subarray minimums/maximums"
- ✅"Trapping rain water" (can also use two pointers)
- ✅"Remove K digits to make smallest number"
- ✅For each element, find the nearest element satisfying a comparison
Don't use Monotonic Stack when...
- ❌You need the global min/max (just scan once or use a variable)
- ❌You need the kth largest/smallest (use a heap)
- ❌The problem involves pairs with a sum/product target (use two pointers or hashing)
- ❌You need contiguous subarray sum (use sliding window or prefix sum)
- ❌The comparison involves two different arrays without a clear pairing
- ❌You need all subarrays explicitly (monotonic stack gives aggregate results)
Monotonic Stack vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Monotonic Stack | Next/previous greater/smaller, spans, histogram areas | Maintains sorted order in stack; each element pushed & popped once → O(n) |
| Regular Stack | Bracket matching, expression evaluation, undo/redo | No ordering constraint — just LIFO for nesting/pairing |
| Monotonic Deque | Sliding window min/max with fixed window size | Pops from both ends; maintains candidates within a window |
| Heap / Priority Queue | Global kth largest, merge k sorted streams | Gives global ordering, not positional next/previous relationships |
| Two Pointers | Pair sum in sorted data, container problems | Converges from ends; doesn't track 'next greater' relationships |
The mental test
Ask yourself: "For each element, do I need to find the nearest element to its left or right that is greater (or smaller)?" If yes, it's a monotonic stack problem. The word "nearest" combined with a comparison is the strongest signal.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using a concrete example: Next Greater Element — for each element in an array, find the first element to its right that is strictly greater. If none exists, the answer is -1.
Brute Force — Scan Right for Every Element
For each element, scan every element to its right until you find one that's greater. This works but is painfully slow for large inputs because of the nested loop.
function nextGreaterElement(nums: number[]): number[] { const result: number[] = new Array(nums.length).fill(-1); for (let i = 0; i < nums.length; i++) { for (let j = i + 1; j < nums.length; j++) { if (nums[j] > nums[i]) { result[i] = nums[j]; break; // found the FIRST greater element } } } return result; } // Input: [2, 1, 2, 4, 3] // Output: [4, 2, 4, -1, -1] // Problem: For each element, we might scan almost the entire array.
Key Observation — Elements That Get 'Blocked'
Notice something: if we have [3, 1, 2, 5], when we reach 5, it's the answer for 3, 1, AND 2. All three were 'waiting' for a greater element, and 5 resolves all of them at once. We don't need to re-scan — we just need to remember who's still waiting.
Array: [3, 1, 2, 5] Waiting list after processing each element (left to right): After 3: waiting = [3] — 3 has no answer yet After 1: waiting = [3, 1] — 1 < 3, so 1 also waits After 2: waiting = [3, 2] — 2 > 1, so 1's answer is 2. Pop 1. 2 < 3, so 2 waits behind 3. After 5: waiting = [] — 5 > 2, so 2's answer is 5. Pop 2. 5 > 3, so 3's answer is 5. Pop 3. The "waiting list" is a stack that stays in decreasing order! That's a monotonic decreasing stack.
Optimal — Monotonic Stack
Maintain a stack of indices whose next greater element hasn't been found yet. The stack stays in decreasing order of values. When a new element is greater than the stack top, pop and record the answer. Each element is pushed once and popped once → O(n) total.
function nextGreaterElement(nums: number[]): number[] { const n = nums.length; const result: number[] = new Array(n).fill(-1); const stack: number[] = []; // stores indices for (let i = 0; i < n; i++) { // Pop all elements that are SMALLER than nums[i] // For each popped element, nums[i] is their next greater while (stack.length > 0 && nums[stack[stack.length - 1]] < nums[i]) { const idx = stack.pop()!; result[idx] = nums[i]; } stack.push(i); } // Elements remaining in stack have no next greater → already -1 return result; } // Each element pushed once, popped at most once → O(n) total.
Why Does the Optimization Work?
From O(n²) to O(n) — The Amortized Argument
In the brute force, we scan right for every element — potentially O(n) work per element. With the monotonic stack, each element is pushed exactly once and popped at most once. That's at most 2n operations total, regardless of the input. The key: popping resolves the answer immediately, so we never re-scan.
Why Decreasing Order?
The stack stays in decreasing order because we only push when the current element is ≤ the stack top. When a larger element arrives, it 'resolves' all smaller elements on top. The decreasing order ensures that the first element to get resolved is the one closest to the current position — exactly the 'next greater' relationship.
The General Principle
Monotonic stacks work because they exploit a dominance relationship: a larger element 'dominates' smaller ones to its left. Once dominated, a smaller element will never be the answer for anything further right. So we can safely discard it (pop it) after recording its answer.
The thinking process matters more than the code
In interviews, explain the brute force first, then say: "I notice that when a large element appears, it resolves multiple waiting elements at once. I can use a stack to track elements still waiting for their answer. The stack stays in decreasing order, and each element is processed at most twice — once when pushed, once when popped. That gives me O(n)."
Core Templates
Monotonic stack problems have four main templates based on two choices: (1) are you looking for the next or previous element? (2) are you looking for greater or smaller? Memorize these — every problem is a variation.
Template 1: Next Greater Element (Decreasing Stack)
Scan left to right. The stack holds indices of elements waiting for their next greater. Stack values are in decreasing order (bottom to top).
function nextGreater(nums: number[]): number[] { const n = nums.length; const result = new Array(n).fill(-1); const stack: number[] = []; // indices, values in decreasing order for (let i = 0; i < n; i++) { while (stack.length && nums[stack[stack.length - 1]] < nums[i]) { result[stack.pop()!] = nums[i]; // nums[i] is the next greater } stack.push(i); } return result; } // Stack invariant: values at stack indices are DECREASING // When nums[i] > stack top → pop (found next greater for that element)
Template 2: Next Smaller Element (Increasing Stack)
Same structure, but the stack holds elements waiting for their next smaller. Stack values are in increasing order. Pop when the current element is smaller.
function nextSmaller(nums: number[]): number[] { const n = nums.length; const result = new Array(n).fill(-1); const stack: number[] = []; // indices, values in increasing order for (let i = 0; i < n; i++) { while (stack.length && nums[stack[stack.length - 1]] > nums[i]) { result[stack.pop()!] = nums[i]; // nums[i] is the next smaller } stack.push(i); } return result; } // Stack invariant: values at stack indices are INCREASING // When nums[i] < stack top → pop (found next smaller for that element)
Template 3: Previous Greater Element (Decreasing Stack)
Scan left to right. For each element, the answer is whatever is currently on top of the stack after popping smaller elements. The stack top is the nearest previous element that is greater.
function prevGreater(nums: number[]): number[] { const n = nums.length; const result = new Array(n).fill(-1); const stack: number[] = []; // indices, values in decreasing order for (let i = 0; i < n; i++) { // Pop elements that are NOT greater than nums[i] while (stack.length && nums[stack[stack.length - 1]] <= nums[i]) { stack.pop(); } // Stack top (if exists) is the previous greater element if (stack.length) { result[i] = nums[stack[stack.length - 1]]; } stack.push(i); } return result; } // Key difference from "next greater": the ANSWER is read from the stack // top, not assigned during the pop. The pop just cleans up irrelevant elements.
Template 4: Previous Smaller Element (Increasing Stack)
Scan left to right. Pop elements that are ≥ current. The remaining stack top is the nearest previous smaller element.
function prevSmaller(nums: number[]): number[] { const n = nums.length; const result = new Array(n).fill(-1); const stack: number[] = []; // indices, values in increasing order for (let i = 0; i < n; i++) { while (stack.length && nums[stack[stack.length - 1]] >= nums[i]) { stack.pop(); } if (stack.length) { result[i] = nums[stack[stack.length - 1]]; } stack.push(i); } return result; } // Stack invariant: values at stack indices are INCREASING // Stack top after cleanup = nearest previous smaller element
Quick Reference: Which Template?
| Looking for... | Stack Order | Pop Condition | Answer From |
|---|---|---|---|
| Next Greater | Decreasing ↓ | stack top < current | Pop assigns current as answer |
| Next Smaller | Increasing ↑ | stack top > current | Pop assigns current as answer |
| Previous Greater | Decreasing ↓ | stack top ≤ current | Stack top IS the answer |
| Previous Smaller | Increasing ↑ | stack top ≥ current | Stack top IS the answer |
The memory trick
For "next" problems, the answer is discovered during the pop — the current element is the answer for the popped element. For "previous" problems, the answer is read from the stack top — whatever survives the cleanup is the answer for the current element.
Variations & Sub-Patterns
The four templates cover the basics, but real interview problems layer additional complexity on top. Here are the most important variations.
| Variation | Twist on the Template | Classic Example |
|---|---|---|
| Circular Array | Process the array twice (2n iterations) to handle wrap-around | Next Greater Element II (LC 503) |
| Stock Span | Previous greater gives the span — distance to the last higher price | Online Stock Span (LC 901) |
| Histogram Area | For each bar, find previous & next smaller to compute width × height | Largest Rectangle in Histogram (LC 84) |
| Matrix Rectangle | Build histogram row by row, apply histogram technique per row | Maximal Rectangle (LC 85) |
| Subarray Min/Max Sum | For each element, find how many subarrays it's the min/max of using prev/next smaller | Sum of Subarray Minimums (LC 907) |
| Remove Digits | Greedy + monotonic stack: remove digits that break increasing order | Remove K Digits (LC 402) |
| Trapping Rain Water | Stack tracks bars; when a taller bar arrives, compute trapped water layer by layer | Trapping Rain Water (LC 42) |
Deep Dive: Largest Rectangle in Histogram
This is the most important monotonic stack problem. For each bar, the largest rectangle using that bar as the shortest has width = (next smaller index) - (previous smaller index) - 1. We find both boundaries using a monotonic increasing stack.
Problems mentioned above
function largestRectangleArea(heights: number[]): number { const n = heights.length; const stack: number[] = []; // increasing stack of indices let maxArea = 0; for (let i = 0; i <= n; i++) { // Use 0 as a sentinel to flush remaining bars const currHeight = i < n ? heights[i] : 0; while (stack.length && heights[stack[stack.length - 1]] > currHeight) { const height = heights[stack.pop()!]; // Width: from previous smaller (stack top) to current (next smaller) const width = stack.length ? i - stack[stack.length - 1] - 1 : i; maxArea = Math.max(maxArea, height * width); } stack.push(i); } return maxArea; } // For each bar: area = height × (nextSmaller - prevSmaller - 1) // The stack gives us both boundaries in one pass. // Sentinel value 0 at the end forces all remaining bars to be processed.
Deep Dive: Sum of Subarray Minimums
For each element, count how many subarrays it's the minimum of. If element at index i has previous-smaller at index left and next-smaller at index right, then it's the minimum of (i - left) × (right - i) subarrays. Multiply by its value and sum everything up.
function sumSubarrayMins(arr: number[]): number { const MOD = 1e9 + 7; const n = arr.length; const prevSmaller = new Array(n).fill(-1); // index of prev smaller const nextSmaller = new Array(n).fill(n); // index of next smaller const stack: number[] = []; // Previous smaller (strictly less) for (let i = 0; i < n; i++) { while (stack.length && arr[stack[stack.length - 1]] >= arr[i]) { stack.pop(); } prevSmaller[i] = stack.length ? stack[stack.length - 1] : -1; stack.push(i); } stack.length = 0; // Next smaller (strictly less — use <= to handle duplicates) for (let i = n - 1; i >= 0; i--) { while (stack.length && arr[stack[stack.length - 1]] > arr[i]) { stack.pop(); } nextSmaller[i] = stack.length ? stack[stack.length - 1] : n; stack.push(i); } let sum = 0; for (let i = 0; i < n; i++) { const left = i - prevSmaller[i]; // subarrays extending left const right = nextSmaller[i] - i; // subarrays extending right sum = (sum + arr[i] * left * right) % MOD; } return sum; } // Key: use strict < for one direction and <= for the other // to avoid double-counting when there are duplicate values.
Circular arrays
For circular "next greater" problems, iterate through the array twice (indices 0 to 2n-1) and use i % n to wrap around. This ensures every element gets a chance to see elements that come "before" it in the circular order.
Visual Walkthrough
Let's trace through two problems step by step to see the monotonic stack in action.
Next Greater Element — Step by Step
Input: [4, 2, 1, 5, 3] Stack stores INDICES. We show values in parentheses for clarity. i=0, nums[0]=4 Stack empty → push 0 Stack: [0(4)] i=1, nums[1]=2 2 < 4 → don't pop → push 1 Stack: [0(4), 1(2)] i=2, nums[2]=1 1 < 2 → don't pop → push 2 Stack: [0(4), 1(2), 2(1)] i=3, nums[3]=5 5 > 1 → pop 2 → result[2] = 5 (1's next greater is 5) 5 > 2 → pop 1 → result[1] = 5 (2's next greater is 5) 5 > 4 → pop 0 → result[0] = 5 (4's next greater is 5) Stack empty → push 3 Stack: [3(5)] i=4, nums[4]=3 3 < 5 → don't pop → push 4 Stack: [3(5), 4(3)] Done. Remaining in stack → no next greater → -1. Result: [5, 5, 5, -1, -1] Key observation: When 5 arrived, it resolved THREE elements at once. That's the power of the monotonic stack — batch resolution.
Largest Rectangle in Histogram — Step by Step
Heights: [2, 1, 5, 6, 2, 3] Append sentinel 0: [2, 1, 5, 6, 2, 3, 0] Stack stores indices. Increasing stack (by height). i=0, h=2: Stack empty → push 0. Stack: [0] i=1, h=1: 1 < 2 → pop 0. height=2, width=1 (no stack left, so width=i=1) area = 2×1 = 2. maxArea=2 push 1. Stack: [1] i=2, h=5: 5 > 1 → push 2. Stack: [1, 2] i=3, h=6: 6 > 5 → push 3. Stack: [1, 2, 3] i=4, h=2: 2 < 6 → pop 3. height=6, width = 4-2-1 = 1. area=6. maxArea=6 2 < 5 → pop 2. height=5, width = 4-1-1 = 2. area=10. maxArea=10 ★ 2 ≥ 1 → stop. push 4. Stack: [1, 4] i=5, h=3: 3 > 2 → push 5. Stack: [1, 4, 5] i=6, h=0 (sentinel): flushes everything. pop 5: height=3, width=6-4-1=1. area=3. pop 4: height=2, width=6-1-1=4. area=8. pop 1: height=1, width=6 (stack empty). area=6. Final maxArea = 10 (bar of height 5, spanning indices 2-3) The sentinel (height 0) guarantees all bars get processed. Width formula: stack empty → width = i. Otherwise → i - stack.top - 1.
Daily Temperatures — Step by Step
Temps: [73, 74, 75, 71, 69, 72, 76, 73] Stack stores indices. Decreasing stack (by temperature). i=0, t=73: push 0. Stack: [0(73)] i=1, t=74: 74>73 → pop 0 → result[0]=1-0=1. push 1. Stack: [1(74)] i=2, t=75: 75>74 → pop 1 → result[1]=2-1=1. push 2. Stack: [2(75)] i=3, t=71: 71<75 → push 3. Stack: [2(75), 3(71)] i=4, t=69: 69<71 → push 4. Stack: [2(75), 3(71), 4(69)] i=5, t=72: 72>69 → pop 4 → result[4]=5-4=1. 72>71 → pop 3 → result[3]=5-3=2. 72<75 → stop. push 5. Stack: [2(75), 5(72)] i=6, t=76: 76>72 → pop 5 → result[5]=6-5=1. 76>75 → pop 2 → result[2]=6-2=4. push 6. Stack: [6(76)] i=7, t=73: 73<76 → push 7. Stack: [6(76), 7(73)] Remaining → result stays 0 (no warmer day). Result: [1, 1, 4, 2, 1, 1, 0, 0] Notice: result[i] = j - i (distance), not the temperature itself. This is a "next greater" problem where the answer is the INDEX DISTANCE.
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of the monotonic stack. Solve them in order.
LC 496. Next Greater Element I
Why this pattern:
The purest monotonic stack problem. Given two arrays, find the next greater element for each element of nums1 in nums2. Teaches the basic decreasing stack template with a hash map for lookups.
Key idea: Build a next-greater map for all elements in nums2 using a monotonic decreasing stack. Then look up each element of nums1 in the map. The stack processes nums2 in O(n), and each lookup is O(1).
LC 739. Daily Temperatures
Why this pattern:
Next greater element where the answer is the INDEX DISTANCE, not the value. The stack stores indices, and when you pop, the answer is current_index - popped_index.
Key idea: Maintain a decreasing stack of indices. When temperature[i] > temperature[stack.top], pop and set result[popped] = i - popped. This is the 'how many days until warmer' question — a direct application of next greater.
LC 901. Online Stock Span
Why this pattern:
Previous greater element in disguise. The span is the number of consecutive days (including today) where the price was ≤ today's price. That's the distance to the previous greater price.
Key idea: Maintain a decreasing stack of (price, index) pairs. For each new price, pop all entries with price ≤ current. The span = current_index - stack.top.index (or current_index + 1 if stack is empty). This is a streaming/online variant — you process one element at a time.
LC 503. Next Greater Element II
Why this pattern:
Circular array variant. The array wraps around, so the next greater element for the last element might be at the beginning. Solved by iterating through the array twice (2n iterations).
Key idea: Loop from i = 0 to 2n-1, using i % n for the actual index. Only push during the first pass (i < n). Pop as usual — the circular iteration ensures every element sees all possible candidates.
LC 907. Sum of Subarray Minimums
Why this pattern:
Contribution technique: for each element, count how many subarrays it's the minimum of. Uses both previous-smaller and next-smaller boundaries. Requires careful handling of duplicates.
Key idea: For element at index i: left = i - prevSmaller[i], right = nextSmaller[i] - i. Contribution = arr[i] × left × right. Use strict < for one direction and ≤ for the other to avoid double-counting duplicates.
LC 84. Largest Rectangle in Histogram
Why this pattern:
The crown jewel of monotonic stack problems. For each bar, find the widest rectangle where that bar is the shortest. Width = nextSmaller - prevSmaller - 1. Uses an increasing stack with a sentinel.
Key idea: Maintain an increasing stack. When a shorter bar arrives, pop and compute area = popped_height × width. Width = i - stack.top - 1 (or i if stack is empty). Append a sentinel bar of height 0 to flush all remaining bars.
LC 85. Maximal Rectangle
Why this pattern:
Builds on the histogram problem. Treat each row of the binary matrix as the base of a histogram — heights[j] = number of consecutive 1s above (including current row). Apply LC 84 to each row's histogram.
Key idea: For each row, update heights: if matrix[i][j] = '1', heights[j]++; else heights[j] = 0. Then run largestRectangleArea(heights) for that row. The answer is the maximum across all rows.
Practice strategy
For each problem: (1) Identify whether you need next/previous and greater/smaller. (2) Decide if the stack should be increasing or decreasing. (3) Determine whether the answer comes from the pop or the stack top. (4) After solving, write: "This is [next/prev] [greater/smaller] because [signal]."
Common Mistakes
These are the traps that catch people on monotonic stack problems. Learn them here so you don't learn them in an interview.
Using the wrong stack order (increasing vs decreasing)
You use a decreasing stack when you need an increasing one, or vice versa. The results are completely wrong and hard to debug because the code 'looks right.'
✅Remember: for 'next GREATER', you need a DECREASING stack (pop smaller elements). For 'next SMALLER', you need an INCREASING stack (pop larger elements). The stack order is the OPPOSITE of what you're looking for.
Storing values instead of indices in the stack
You push values onto the stack but then can't compute distances (like 'how many days until warmer') because you don't know the positions.
✅Almost always store INDICES in the stack. You can always look up the value with nums[stack[stack.length - 1]]. Indices give you both the value AND the position.
Forgetting the sentinel in histogram problems
After processing all bars, some bars remain in the stack unprocessed. You miss rectangles that extend to the right edge of the histogram.
✅Append a sentinel bar of height 0 at the end. This forces every remaining bar to be popped and processed. Alternatively, add a cleanup loop after the main loop.
Double-counting with duplicate values
In 'Sum of Subarray Minimums', duplicate values cause the same subarray to be counted twice because both copies claim to be the minimum.
✅Use strict inequality (<) for one direction and non-strict (≤) for the other. This breaks ties consistently: one copy 'owns' subarrays to the left, the other 'owns' subarrays to the right.
Confusing 'next' vs 'previous' template logic
For 'next' problems, the answer is assigned during the pop. For 'previous' problems, the answer is read from the stack top. Mixing these up gives wrong results.
✅'Next': when you pop element X because of element Y, Y is X's next greater/smaller. 'Previous': after popping, whatever is left on top is the current element's previous greater/smaller.
Wrong width calculation in histogram
You compute width as i - popped_index, but the correct width is i - stack.top - 1 (or i if the stack is empty). Off-by-one errors change the answer significantly.
✅The width extends from (previous smaller + 1) to (next smaller - 1). Previous smaller is the new stack top after popping. So width = i - stack.top - 1. If stack is empty, the bar extends all the way to the left: width = i.
Interview Strategy
Monotonic stack problems are a favorite at FAANG companies because they test whether you can see through a problem to its underlying structure. Here's how to present your thinking.
The 5-Step Interview Flow
Identify the Core Question
Reframe the problem: 'For each element, I need to find the nearest [greater/smaller] element to its [left/right].' If you can phrase it this way, you know it's a monotonic stack problem. Say this out loud to the interviewer.
State the Brute Force
'The brute force is O(n²) — for each element, scan left/right to find the answer. But I notice that elements get 'resolved' in batches when a dominant element arrives. I can use a monotonic stack to track unresolved elements.'
Explain the Stack Invariant
'I'll maintain a stack in [decreasing/increasing] order. When a new element violates the order, I pop — and the pop itself gives me the answer. Each element is pushed once and popped once, so the total time is O(n).'
Code It Clean
Use clear variable names. Store indices, not values. Add a comment for the stack invariant. For histogram problems, mention the sentinel. Keep the while-pop loop tight and readable.
Test & Analyze
Trace through a small example showing pushes and pops. Verify edge cases: all increasing, all decreasing, all equal, single element. State complexity: O(n) time (each element pushed/popped once), O(n) space for the stack.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "What if the array is circular?" | Iterate 2n times, use i % n for indexing. Only push during the first n iterations. This handles wrap-around naturally. |
| "Can you do it in O(1) space?" | For some problems (like Daily Temperatures), you can iterate right-to-left and use the result array itself to skip ahead. But the standard O(n) stack solution is usually expected and accepted. |
| "What about duplicates?" | Use strict < for one direction and ≤ for the other to break ties consistently. Explain why: it prevents double-counting by assigning ownership of boundary subarrays to exactly one copy. |
| "Extend to 2D (matrix)?" | Build a histogram per row (heights of consecutive 1s), then apply the 1D histogram technique to each row. This is how Maximal Rectangle (LC 85) works. |
| "What if elements are streaming (online)?" | The stack naturally supports online processing — you push each new element and pop as needed. Stock Span (LC 901) is exactly this: process one element at a time, maintain the stack across calls. |
| "Why not use a priority queue instead?" | A heap gives you the global min/max, but not the NEAREST greater/smaller by position. The monotonic stack preserves positional ordering, which is what 'next/previous' problems need. |
The meta-skill
The hardest part of monotonic stack problems isn't the code — it's recognizing that the problem reduces to "next/previous greater/smaller." Problems like Largest Rectangle in Histogram and Trapping Rain Water don't mention stacks at all. Practice reframing problems in terms of nearest dominant elements — that's the real interview skill.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: A stack that maintains sorted order (increasing or decreasing). Pops pair elements with their nearest dominant neighbor → O(n)
Why O(n)?: Each element is pushed once and popped at most once. Total operations ≤ 2n regardless of input
Next greater: Decreasing stack. Pop when current > top. Popped element's answer = current element
Next smaller: Increasing stack. Pop when current < top. Popped element's answer = current element
Previous greater: Decreasing stack. After popping, stack top IS the answer for current element
Previous smaller: Increasing stack. After popping, stack top IS the answer for current element
Store indices: Almost always push indices, not values. Indices give you position AND value via nums[i]
Histogram trick: For each bar: area = height × (nextSmaller - prevSmaller - 1). Use sentinel height 0 to flush
Circular arrays: Iterate 2n times with i % n. Only push during first n. Handles wrap-around
Duplicate handling: Use < for one direction, ≤ for the other. Prevents double-counting
Contribution technique: Element i is min of (i - prevSmaller) × (nextSmaller - i) subarrays
Mental trigger: "Nearest" + "greater/smaller" + "left/right" → monotonic stack. Also: histogram, span, temperatures
Decision Flowchart
What does the problem ask? ├── "Next greater element" for each position? │ → Decreasing stack, scan left→right, answer on POP ├── "Next smaller element" for each position? │ → Increasing stack, scan left→right, answer on POP ├── "Previous greater element" for each position? │ → Decreasing stack, scan left→right, answer = STACK TOP ├── "Previous smaller element" for each position? │ → Increasing stack, scan left→right, answer = STACK TOP ├── "Largest rectangle in histogram"? │ → Increasing stack + sentinel. Area = height × width on pop ├── "Sum of subarray mins/maxs"? │ → Find prev & next smaller/greater. Contribution = val × left × right ├── "Stock span" or "days until warmer"? │ → Previous/next greater. Answer = index distance ├── Circular array? │ → Same template but iterate 2n times with i % n └── Not sure? → Ask: "Can I rephrase this as 'nearest greater/smaller'?" ├── YES → Pick the right template above └── NO → Probably not a monotonic stack problem
One last thing
Monotonic stack is one of the highest-ROI patterns for interviews. It appears in easy problems (Next Greater Element I) and hard ones (Largest Rectangle, Maximal Rectangle). The technique is always the same — maintain a sorted stack, pop when violated, and the pop gives you the answer. Master the 4 templates in Section 04, solve the 7 problems in Section 07, and you'll recognize this pattern instantly in any interview.