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
✦ Definition~90s read
What is 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.
★
A monotonic stack is a regular stack with one constraint: elements are always in strictly increasing or decreasing order.
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.
Plain-English First
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.
Mental Model
Mental Model: Stack as a 'Waiting Line'
Think of the stack as a line of elements waiting for their answer — the next greater or smaller element that will eventually pop them.
Each element enters the stack when it's pushed.
It waits until a new element 'beats' it (greater for decreasing stack, smaller for increasing).
Elements that never get beaten have no answer (return -1).
📊 Production Insight
Confusing stack direction breaks the entire solution.
Always ask: 'Am I looking for greater or smaller?' Decreasing → greater, Increasing → smaller.
Wrong direction produces completely wrong output no matter how clean your code.
🎯 Key Takeaway
Monotonic stack = stack + invariant.
Decreasing → next greater. Increasing → next smaller.
O(n) time because each element enters and leaves at most once.
Choosing Stack Direction
IfFind next/previous greater element (strict or non-strict)
→
UseUse monotonic decreasing stack (pop when current > top).
IfFind next/previous smaller element (strict or non-strict)
→
UseUse monotonic increasing stack (pop when current < top).
IfNeed to compute width, span, or distance
→
UseStore indices in stack, not values.
thecodeforge.io
Monotonic Stack
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.
monotonic_stack.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from typing importListdefnext_greater_element(nums: List[int]) -> List[int]:
"""
For each element, find the next strictly greater element to its right.
Returns -1if 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 inrange(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 -1return 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]=-1defstock_span(prices: List[int]) -> List[int]:
"""
StockSpan: 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 orderfor i, price inenumerate(prices):
while stack and price >= stack[-1][1]:
stack.pop()
span[i] = i + 1ifnot 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]
Output
[4, 2, 4, -1, -1]
[1, 1, 1, 2, 1, 4, 6]
💡Java Version — Same Pattern
In Java, store indices in a Deque<Integer> (ArrayDeque) and use peekLast()/pollLast() for stack operations. The algorithm is identical.
📊 Production Insight
Stock span bug: using > instead of >= means equal prices break the span.
Equal prices should count as consecutive days — always use >= for 'previous less or equal'.
One wrong operator yields completely wrong financial calculations.
🎯 Key Takeaway
Monotonic decreasing: pop when current > top for next greater.
For stock span (previous greater or equal): pop when current >= top.
Always verify strict vs non-strict with problem statement.
Strict vs Non-Strict Pop Condition
IfStrictly greater (next greater element)
→
UseUse > in while condition.
IfGreater or equal (stock span)
→
UseUse >= to pop equal heights.
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²).
monotonic_increasing_stack.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from typing importListdefnext_smaller_element(nums: List[int]) -> List[int]:
"""
MonotonicINCREASING 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 inrange(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]deflargest_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 popfor i, h inenumerate(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)
Output
[2, 1, 1, -1, -1]
10
⚠ Sentinel Removal Trap
Forgetting the sentinel 0 will make you miss rectangles that extend to the end. Always append 0 (or -1 for decreasing stacks) to flush remaining elements.
📊 Production Insight
Largest rectangle: forgetting sentinel 0 leaves remaining heights unprocessed.
If you skip sentinel, you must manually drain the stack — easy to forget.
Sentinel guarantees all bars pop and contribute their area.
🎯 Key Takeaway
Monotonic increasing: pop when current < top for next smaller.
Largest rectangle uses increasing stack + sentinel to flush remaining bars.
Width = current index - popped bar's original start index.
Flushing the Stack
IfUsing sentinel 0 at end of heights
→
UseAll bars are processed inside the loop.
IfNo sentinel
→
UseAfter loop, while stack not empty, pop and compute area with width = n - index.
thecodeforge.io
Monotonic Stack
Variations and Nuances
Beyond next element problems, monotonic stack adapts to
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.
trapping_rain_water.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
deftrap(heights: List[int]) -> int:
"""
Trapping rain water using monotonic decreasing stack.
Time: O(n), Space: O(n)
"""
water = 0
stack = [] # indices, decreasing heightsfor i, h inenumerate(heights):
while stack and h > heights[stack[-1]]:
bottom_idx = stack.pop()
ifnot stack:
break
left_idx = stack[-1]
width = i - left_idx - 1
height = min(heights[left_idx], h) - heights[bottom_idx]
water += width * height
stack.append(i)
return water
Output
6 # for heights [0,1,0,2,1,0,1,3,2,1,2,1]
🔥Rain Water Trick
The stack stores indices of decreasing heights. When a higher bar appears, it pops the trough and computes trapped water using the left and right boundaries.
📊 Production Insight
In production code (e.g., stock market analysis), off-by-one in span causes incorrect profit calculations.
Always verify with duplicate values in test data to catch strict/non-strict bugs.
One character change = hours of debugging if missed.
🎯 Key Takeaway
Strict vs non-strict: problem wording decides.
Decreasing: > for strict, >= for non-strict.
Increasing: < for strict, <= for non-strict.
Test with equal elements: [2,2,2] reveals the bug fast.
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:
debug_monotonic.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defdebug_nge(nums):
n, res = len(nums), [-1]*len(nums)
stack = []
for i inrange(n):
print(f'i={i}, val={nums[i]}, stack_before={stack}')
while stack and nums[i] > nums[stack[-1]]:
idx = stack.pop()
res[idx] = nums[i]
print(f' pop {idx}, set res[{idx}]={nums[i]}, stack={stack}')
stack.append(i)
print(f' push {i}, stack_after={stack}')
print(f'final res: {res}')
return res
debug_nge([2,1,2,4,3])
Output
i=0, val=2, stack_before=[]
push 0, stack_after=[0]
i=1, val=1, stack_before=[0]
push 1, stack_after=[0,1]
i=2, val=2, stack_before=[0,1]
pop 1, set res[1]=2, stack=[0]
push 2, stack_after=[0,2]
i=3, val=4, stack_before=[0,2]
pop 2, set res[2]=4, stack=[0]
pop 0, set res[0]=4, stack=[]
push 3, stack_after=[3]
i=4, val=3, stack_before=[3]
push 4, stack_after=[3,4]
final res: [4,2,4,-1,-1]
📊 Production Insight
During an onsite interview, one engineer spent 20 minutes debugging 'next greater' output because they used < instead of >.
Lesson: always write a mental trace for a small array before coding.
A single comparison error invalidates all results — but won't crash your program.
🎯 Key Takeaway
Store indices, not values.
Trace manually with [2,1,2] before running code.
Debug by printing stack + result after each iteration.
Real-World Applications: Beyond LeetCode
Monotonic stacks aren't just for interviews. They appear in
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.
Mental Model
Mental Model: The 'Delayed Answer'
Each element enters the stack because its answer isn't known yet. It leaves when an answer arrives.
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.
📊 Production Insight
In a real-time stock feed, using a monotonic stack for span calculation reduces latency from milliseconds to microseconds compared to nested loops.
But you must handle streaming data: the stack persists across incoming ticks, so reset it properly at market open.
Memory leak risk: unbounded stack growth if not reset daily.
🎯 Key Takeaway
Monotonic stack solves 'element relationship' problems in O(n).
Real uses: finance, databases, graphics.
Pattern: iterate, enforce order, pop computes answer.
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.
NextGreaterElement.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — dsa tutorialint[] findNextGreater(int[] prices) {
int[] result = newint[prices.length];
Arrays.fill(result, -1);
Deque<Integer> stack = newArrayDeque<>();
for (int i = 0; i < prices.length; i++) {
while (!stack.isEmpty() && prices[stack.peek()] < prices[i]) {
result[stack.pop()] = prices[i];
}
stack.push(i);
}
return result;
}
Output
prices = [73, 74, 75, 71, 69, 72, 76, 73]
result = [74, 75, 76, 72, 72, 76, -1, -1]
⚠ Production Trap:
Using a Java Stack class (Vector-based) instead of ArrayDeque kills performance with synchronized overhead. Always use ArrayDeque for monotonic stack problems.
🎯 Key Takeaway
Monotonic stack = O(n) time when nested loops give you O(n²). The stack discards dead candidates automatically.
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.
LargestRectangle.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — dsa tutorialintlargestRectangleInHistogram(int[] heights) {
Deque<Integer> stack = newArrayDeque<>();
int maxArea = 0;
for (int i = 0; i <= heights.length; i++) {
int h = (i == heights.length) ? 0 : heights[i];
while (!stack.isEmpty() && h < heights[stack.peek()]) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
Output
heights = [2, 1, 5, 6, 2, 3]
maxArea = 10
💡Senior Shortcut:
Append a sentinel (0) to your array to force stack flush. Saves you from writing a separate cleanup loop.
🎯 Key Takeaway
Decreasing stack finds next greater elements. Increasing stack finds next smaller. Get the polarity wrong and you'll debug for hours.
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.
SumMinSubarrays.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — dsa tutorialintsumSubarrayMins(int[] arr) {
Deque<Integer> stack = newArrayDeque<>();
long sum = 0;
int mod = 1_000_000_007;
for (int i = 0; i <= arr.length; i++) {
while (!stack.isEmpty() && (i == arr.length || arr[stack.peek()] > arr[i])) {
int idx = stack.pop();
int left = stack.isEmpty() ? -1 : stack.peek();
int right = i;
long count = (long)(idx - left) * (right - idx);
sum = (sum + arr[idx] * count) % mod;
}
stack.push(i);
}
return (int)sum;
}
Output
arr = [3, 1, 2, 4]
sum = 17
🔥Performance Note:
Subarray counting uses modular arithmetic. For production systems handling large arrays, precompute factorials or use long before casting to int.
🎯 Key Takeaway
Store indices, not values. The distance between stack positions defines subarray boundaries — that's where the real problem-solving power lives.
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.
SlidingWindowMax.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// io.thecodeforge — dsa tutorialimport java.util.*;
publicclassSlidingWindowMax {
publicint[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 0) returnnewint[0];
int[] result = newint[nums.length - k + 1];
Deque<Integer> stack = newArrayDeque<>();
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[stack.peekLast()] <= nums[i]) {
stack.pollLast();
}
stack.offerLast(i);
if (stack.peekFirst() <= i - k) {
stack.pollFirst();
}
if (i >= k - 1) {
result[i - k + 1] = nums[stack.peekFirst()];
}
}
return result;
}
}
Output
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3, 3, 5, 5, 6, 7]
⚠ Production Trap:
Using a PriorityQueue for sliding window max is O(n log k) and kills throughput at scale. The monotonic deque is O(n). Your infrastructure budget will thank you.
🎯 Key Takeaway
A monotonic stack deque gives O(1) amortized window queries — no heap, no log factor, just index arithmetic.
thecodeforge.io
Monotonic Stack
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.
Store the index (not value) in the map when you need positional arithmetic — e.g., width for histogram rectangles. Value-only maps lose the geometry.
🎯 Key Takeaway
Precompute full monotonic tables (next greater, previous smaller) in O(n), then answer any query in O(1). Never rescan.
● Production incidentPOST-MORTEMseverity: high
Stock Span Calculation Failure Due to Incorrect Operator
Symptom
Stock span values for days with equal prices were consistently off by 1. For prices [100, 90, 90, 80], the span returned [1,1,1,1] instead of [1,1,2,1].
Assumption
The developer assumed 'previous greater' is always strict, and didn't consider equal prices as 'consecutive'.
Root cause
Comparison operator in the while condition was 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.
Fix
Changed while stack and price > stack[-1][1]: to while stack and price >= stack[-1][1]:. Re-ran pipeline with correct results.
Key lesson
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.
Production debug guideCommon symptoms and actions for coding and troubleshooting monotonic stack implementations.5 entries
Symptom · 01
Output array contains -1 for elements that clearly have a next greater/smaller.
→
Fix
Check stack direction: demand greater → use decreasing stack. Check pop condition: for greater, pop when current > top. Verify you're storing indices, not values.
Symptom · 02
Span or width values are off by one or incorrect for equal elements.
→
Fix
Verify strict vs non-strict operator. For 'previous greater or equal', use >=. For 'strictly greater', use >. Test with an array of identical values.
Symptom · 03
In Largest Rectangle, area calculation misses some bars (returns lower than possible).
→
Fix
Ensure sentinel 0 is appended at the end to flush the stack. If not using sentinel, manually pop all remaining heights and compute area with width = len(heights) - index.
Symptom · 04
Infinite loop or stack overflow.
→
Fix
Unlikely with proper loop, but check that you always push the current index after the while loop. Also ensure sentinel is not missing — if heights are appended with sentinel, the loop will exit.
Symptom · 05
Rain water result is zero for obvious valleys.
→
Fix
Check that the stack stores indices, not heights. Ensure the pop condition is current > heights[top] for decreasing stack. Verify you compute width correctly as i - left_idx - 1.
★ Monotonic Stack Debug Cheat SheetQuick steps to diagnose monotonic stack implementation issues during coding or interview.
Wrong next greater output−
Immediate action
Check if stack is decreasing or increasing. Confirm pop condition.
Commands
Print stack after each iteration: print(f'i={i}, stack={[nums[idx] for idx in stack]}')
Print result array: print(f'result={result}')
Fix now
If wrong direction, flip comparison. If wrong strictness, adjust operator.
Span values incorrect+
Immediate action
Test with array of equal values. Verify strict vs non-strict.
Commands
Run with [5,5,5] — expected spans [1,2,3] for previous >= current.
Always store the index of the element, not its value. Retrieve value via nums[index] when needed.
×
Forgetting sentinel in Largest Rectangle in Histogram
Symptom
Some rectangles not considered; max area is lower than actual.
Fix
Append a 0 (or -1 for decreasing) to the end of the heights array to force all remaining bars to pop.
×
Using wrong strictness for equal elements
Symptom
Off-by-one errors in span or next greater when duplicates exist.
Fix
Identify if problem requires 'strictly greater/smaller' or 'greater/smaller or equal'. Adjust pop condition accordingly.
×
Confusing increasing vs decreasing stack direction
Symptom
Complete wrong output — e.g., getting smaller elements when expected greater.
Fix
Remember: decreasing stack for greater, increasing stack for smaller. Draw a small example to confirm before coding.
×
Not processing remaining elements after loop (no sentinel, no manual drain)
Symptom
Some elements retain initial default value -1 when they should have an answer.
Fix
Either use sentinel or add a post-loop that pops remaining stack and sets appropriate answer (e.g., -1 for next greater, but for previous greater you may need to compute from end).
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
Implement Next Greater Element using a monotonic stack.
Q02JUNIOR
What is the time complexity of the monotonic stack approach and why?
Q03JUNIOR
When do you use a monotonic increasing vs decreasing stack?
Q04SENIOR
Solve Largest Rectangle in Histogram in O(n) using monotonic stack.
Q05SENIOR
Explain the difference between strict and non-strict monotonic stack. Pr...
Q06SENIOR
How would you adapt monotonic stack to solve Trapping Rain Water?
Q01 of 06SENIOR
Implement Next Greater Element using a monotonic stack.
ANSWER
Iterate array, maintain decreasing stack of indices. For each element, while stack not empty and current > nums[stack.top], pop and set result[popped] = current. Then push current index. Unprocessed indices stay -1.
Q02 of 06JUNIOR
What is the time complexity of the monotonic stack approach and why?
ANSWER
O(n) — each element is pushed once and popped at most once. The while loop runs in total O(n) across entire iteration.
Q03 of 06JUNIOR
When do you use a monotonic increasing vs decreasing stack?
ANSWER
Use decreasing stack when you need to find the next greater element (pop when current > top). Use increasing stack when you need the next smaller element (pop when current < top).
Q04 of 06SENIOR
Solve Largest Rectangle in Histogram in O(n) using monotonic stack.
ANSWER
Use monotonic increasing stack storing (start_index, height). Append sentinel 0 to heights. Iterate; when current height < top height, pop, compute area = height * (i - idx). Update start to idx. Push (start, current height). Return max area.
Q05 of 06SENIOR
Explain the difference between strict and non-strict monotonic stack. Provide examples where each matters.
ANSWER
Strict uses > or < and pops only when strictly greater/smaller. Non-strict uses >= or <= and also pops on equal. Stock span is non-strict (equal prices count). Next Greater Element on Leetcode is strict. Using the wrong one shifts results by one index for duplicates.
Q06 of 06SENIOR
How would you adapt monotonic stack to solve Trapping Rain Water?
ANSWER
Use a decreasing stack of indices. When a taller bar appears, pop the top (which is a trough). If stack not empty, left boundary is new top. Water = (min(left height, current height) - trough height) × (i - left - 1). Add to total. Repeat until stack empty or decreasing invariant restored.
01
Implement Next Greater Element using a monotonic stack.
SENIOR
02
What is the time complexity of the monotonic stack approach and why?
JUNIOR
03
When do you use a monotonic increasing vs decreasing stack?
JUNIOR
04
Solve Largest Rectangle in Histogram in O(n) using monotonic stack.
SENIOR
05
Explain the difference between strict and non-strict monotonic stack. Provide examples where each matters.
SENIOR
06
How would you adapt monotonic stack to solve Trapping Rain Water?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
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.
Was this helpful?
02
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).
Was this helpful?
03
How do I choose between strict and non-strict comparison?
Read the problem carefully. 'Strictly greater/smaller' uses > or < without equality. 'Greater or equal' uses >= or <=. For example, stock span typically counts consecutive days with price <= today, so non-strict (>=) is correct. Always test with an array of equal values.
Was this helpful?
04
Can I use a monotonic stack for previous element problems without reversing the array?
Yes. For previous greater/smaller, you can either iterate left-to-right and update results when popping (the previous greater is the stack top after pop), or iterate right-to-left with the same direction stack. The pattern is symmetric.
Was this helpful?
05
Is it possible to solve monotonic stack problems in other languages like Java or C++?
Absolutely. In Java, use Deque<Integer> (ArrayDeque) for stack operations. In C++, use std::stack<int>. The algorithm is identical — only syntax changes. The core logic of popping while condition holds and storing indices remains.