Senior 3 min · March 30, 2026

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.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 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
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: Stack as a 'Waiting Line'
  • 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).
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.

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 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]
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 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)
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.

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
def trap(heights: List[int]) -> int:
    """
    Trapping rain water using monotonic decreasing stack.
    Time: O(n), Space: O(n)
    """
    water = 0
    stack = []  # indices, decreasing heights
    for i, h in enumerate(heights):
        while stack and h > heights[stack[-1]]:
            bottom_idx = stack.pop()
            if not 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].

debug_monotonic.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def debug_nge(nums):
    n, res = len(nums), [-1]*len(nums)
    stack = []
    for i in range(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: The 'Delayed Answer'
  • 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.
● 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.
Print condition: print(f'current={prices[i]}, top={stack[-1][1]}, condition={prices[i] >= stack[-1][1]}')
Fix now
If using >, change to >=. If using <, change to <=.
Largest rectangle area missing bars+
Immediate action
Ensure sentinel 0 appended at end of heights list.
Commands
Print heights: print(heights) — verify sentinel present.
After loop, print remaining stack: print(stack) — should be empty.
Fix now
If sentinel missing, add heights.append(0). If using manual drain, loop while stack and compute area.
Rain water returns 0+
Immediate action
Check that you're using a decreasing stack and indices.
Commands
Print each pop: print(f'pop idx={bottom}, left={left}, width={width}, height={height}')
Verify heights array has at least 2 elements.
Fix now
Ensure while condition is h > height[stack[-1]] not >=. Add a check for empty stack after pop.
Monotonic Stack Variants
Problem TypeStack DirectionPop ConditionClassic Examples
Next Greater Element (right)Decreasingcurrent > topDaily Temperatures, NGE
Previous Greater Element (left)Decreasingcurrent >= topStock Span
Next Smaller Element (right)Increasingcurrent < topNext Smaller Element
Previous Smaller Element (left)Increasingcurrent <= topSum of Subarray Minimums
Area / width problemsIncreasingcurrent < topLargest Rectangle, Trapping Rain Water

Key takeaways

1
Monotonic stack = regular stack + the invariant that elements stay in increasing or decreasing order. Violating elements are popped first.
2
Monotonic decreasing stack → next/previous greater element. Monotonic increasing stack → next/previous smaller element.
3
Time complexity is O(n)
each element is pushed at most once and popped at most once regardless of the while loop.
4
Store indices in the stack, not values
you need the index to compute distances, widths, and spans.
5
When you pop an element, you know its 'answer'
the element that triggered the pop is the next greater/smaller for the popped element.
6
Strict vs non-strict comparison
always check problem definition. Test with equal elements to catch off-by-one.
7
For Largest Rectangle, remember to flush stack with a sentinel (height 0) to process all bars.
8
Real-world applications include stock span, rain water trapping, and database query optimisation.

Common mistakes to avoid

5 patterns
×

Storing values instead of indices in the stack

Symptom
Cannot compute span/width/distance; output inconsistent.
Fix
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.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is a monotonic stack?
02
When should I use a monotonic stack?
03
How do I choose between strict and non-strict comparison?
04
Can I use a monotonic stack for previous element problems without reversing the array?
05
Is it possible to solve monotonic stack problems in other languages like Java or C++?
🔥

That's Stack & Queue. Mark it forged?

3 min read · try the examples if you haven't

Previous
Stack vs Heap Memory: What Every Developer Must Know
10 / 10 · Stack & Queue
Next
Binary Tree