Learn/DSA Patterns
DSA PatternsStacksmedium10 min read

Monotonic Stack — Next Greater / Smaller Element

A monotonic stack is a stack kept in strictly increasing or decreasing order by popping elements that violate the order before pushing. This single idea solves 'next greater element', 'daily temperatures', 'previous smaller element', and a dozen interview variants in O(n) — instead of the O(n²) brute-force.

#stack#monotonic#next-greater#next-smaller#daily-temperatures#beginner#interview
Table of contents

Before we start

The monotonic stack is one of those ideas that sounds complicated but rests on a single insight you can state in one sentence. Once you see it, you will recognize it everywhere — in histogram problems, stock prices, and temperature forecasts. By the end you will be able to:

  • Explain why the brute-force O(n²) approach is wasteful and what the stack eliminates.
  • Write the next-greater-element template from memory.
  • Adapt it to next-smaller, previous-greater, and previous-smaller by changing two characters.

Picture this first (no code yet)

A real-life story

Imagine you are standing in a queue watching people join from the back. You want to know: for each person, who is the first taller person standing behind them?

Brute force: for each person, look at everyone behind them until you find someone taller. If the queue has 10,000 people, the last person in line might search through 10,000 others — O(n²) total.

Better idea: keep a "waiting list" (a stack) of people who haven't yet found their taller successor. When a new person arrives and is taller than the person at the top of the waiting list, the waiting-list person has found their answer — remove them and record the result. The new person joins the waiting list.

Each person joins the waiting list once and leaves it once. Total work: O(n). That waiting list is a monotonic stack.


The actual problem

Next Greater Element I (LC #496) / Daily Temperatures (LC #739):

Given an array temps = [73, 74, 75, 71, 69, 72, 76, 73], for each day find the number of days you have to wait until a warmer temperature. Return [1, 1, 4, 2, 1, 1, 0, 0].

In general: for each index i, find the index j > i such that arr[j] > arr[i] and j is as small as possible.


First, the slow way (so you feel the pain)

# O(n²) brute force
n = len(temps)
result = [0] * n
for i in range(n):
    for j in range(i + 1, n):
        if temps[j] > temps[i]:
            result[i] = j - i
            break

For n = 100,000 temperatures, this is 10¹⁰ comparisons in the worst case (a decreasing array). At 10⁸ operations per second, that is 100 seconds. The monotonic stack finishes in under a millisecond.


The turning point

Pause & think

Consider [73, 74, 75, 71, 69, 72, ...].

When we reach index 5 (value 72), we know 72 > 69 and 72 > 71. Both indices 4 and 3 can now be answered! We answer them in reverse order (most recent first).

What data structure processes "most recent unresolved" first and pops them one by one as answers arrive? A stack — last in, first out.

The stack stores indices (not values) of elements that haven't found their "next greater" yet. When a new element arrives and is larger than the stack top, the top gets its answer. Keep popping until the stack top is greater than or equal to the new element.


The one idea to remember

The entire pattern in one sentence

Maintain a stack of "unsatisfied" indices in decreasing order of their values; when a new element is larger than the stack top, the top has found its next greater — pop and record; push the new index.


The template (next greater element)

def dailyTemperatures(temps):
    n      = len(temps)
    result = [0] * n
    stack  = []          # stores indices; values are in decreasing order

    for i, temp in enumerate(temps):
        # while this temp is greater than the temp at the stack's top index
        while stack and temp > temps[stack[-1]]:
            idx         = stack.pop()
            result[idx] = i - idx   # days to wait = current index − popped index

        stack.append(i)  # i hasn't found its answer yet; push it

    # anything left in stack has no warmer day → result stays 0
    return result

Line-by-line narration:

  1. result = [0] * n — default is 0 (no warmer day found), which is the correct answer for elements with no greater successor.
  2. stack = [] — stores indices (not values). Storing indices lets us compute i - idx for the "days to wait" answer.
  3. for i, temp in enumerate(temps): — process left to right.
  4. while stack and temp > temps[stack[-1]]: — this element answers all pending smaller elements. "Decreasing stack" means values at the bottom > values at the top; the top is the smallest pending value. If the current temp beats the top, it beats several tops in a row.
  5. result[idx] = i - idx — record the gap.
  6. stack.append(i) — push current index: it has not yet found its answer.
  7. Remaining stack items: no future element was greater — leave result at 0.

Watch it happen, frame by frame

temps = [73, 74, 75, 71, 69, 72, 76, 73]

i=0, temp=73: stack=[]push 0.           stack=[0]         result=[0,0,0,0,0,0,0,0]
i=1, temp=74: 74>temps[0]=73pop 0, result[0]=1-0=1. Push 1.  stack=[1]
i=2, temp=75: 75>temps[1]=74pop 1, result[1]=2-1=1. Push 2.  stack=[2]
i=3, temp=71: 71<75push 3.             stack=[2,3]
i=4, temp=69: 69<71push 4.             stack=[2,3,4]
i=5, temp=72: 72>temps[4]=69pop 4, result[4]=5-4=1.
              72>temps[3]=71pop 3, result[3]=5-3=2.
              72<temps[2]=75stop. Push 5.  stack=[2,5]
i=6, temp=76: 76>temps[5]=72pop 5, result[5]=6-5=1.
              76>temps[2]=75pop 2, result[2]=6-2=4.
              stack emptyPush 6.        stack=[6]
i=7, temp=73: 73<76push 7.             stack=[6,7]

Loop ends. stack=[6,7]result[6]=result[7]=0 (no warmer day).

Final: [1, 1, 4, 2, 1, 1, 0, 0]

Each index was pushed once and popped once — total 2n operations = O(n).


The four variants (one template, two changes)

The monotonic stack template handles four directional questions. Change > to < (or iterate right to left) to cover all cases:

QuestionStack orderPop conditionIterate
Next GreaterDecreasingcurr > topLeft → Right
Next SmallerIncreasingcurr < topLeft → Right
Previous GreaterDecreasingcurr > topRight → Left
Previous SmallerIncreasingcurr < topRight → Left

Next Smaller (left → right, increasing stack):

def nextSmaller(arr):
    n, result, stack = len(arr), [-1] * n, []
    for i, val in enumerate(arr):
        while stack and val < arr[stack[-1]]:
            result[stack.pop()] = val   # or i, depending on what "answer" means
        stack.append(i)
    return result

Previous Smaller (right → left, increasing stack):

def prevSmaller(arr):
    n, result, stack = len(arr), [-1] * n, []
    for i in range(n - 1, -1, -1):
        while stack and arr[i] < arr[stack[-1]]:
            result[stack.pop()] = arr[i]
        stack.append(i)
    return result

Next Greater Element I (LC #496) — two arrays

When nums2 is the "universe" and nums1 ⊆ nums2, first compute NGE for all of nums2 using the monotonic stack, store in a hashmap {val → nge}, then look up each element of nums1:

def nextGreaterElement(nums1, nums2):
    nge = {}
    stack = []
    for val in nums2:
        while stack and val > stack[-1]:
            nge[stack.pop()] = val
        stack.append(val)
    for v in stack:
        nge[v] = -1   # no greater element
    return [nge[v] for v in nums1]

Here the stack stores values (not indices) because we don't need index gaps — we need the next greater value itself.


Where to spot this pattern

Trigger words:

  • "next greater/smaller element"
  • "first warmer day / first day with lower stock"
  • "visible buildings / people in queue"
  • "span" (stock span problem)
  • anything asking for the nearest element to the left/right satisfying a comparison

5 classic problems:

  1. Daily Temperatures (LC #739): next greater, return day gap.
  2. Next Greater Element I/II (LC #496/503): NGE in another array; NGE in circular array.
  3. Online Stock Span (LC #901): consecutive days with price ≤ today — previous-greater variant.
  4. Sum of Subarray Minimums (LC #907): use prev/next smaller to count subarrays where each element is the minimum.
  5. Largest Rectangle in Histogram (LC #84): uses previous-smaller + next-smaller simultaneously (covered in 5.04).

Common traps

Watch out for these

  • Storing values instead of indices (or vice versa). When the answer is an index gap or a position, store indices. When the answer is the next greater value itself, store values. Read the problem output format carefully.
  • Off-by-one: > vs >= in the pop condition. > gives "strictly next greater." If you use >=, equal elements will also pop — giving you "next greater or equal." The problem statement tells you which one to use.
  • Forgetting to handle remaining stack items. After the loop, elements still in the stack have no next greater element. Their result stays at the default (-1 or 0) — make sure you initialised result correctly before the loop.
  • Circular array (NGE II, LC #503). Iterate 2n times, using i % n as the index. Don't push during the second pass of already-processed elements — or push indices only for i < n.

Remember this forever

Monotonic Stack — Next Greater template

result = [0] * n   # or [-1] * n
stack  = []        # stores indices; decreasing values
for i, val in enumerate(arr):
    while stack and val > arr[stack[-1]]:
        result[stack.pop()] = i   # or val, or i - popped, per problem
    stack.append(i)
# remaining stack → no answer (default stays)

Four variants: change > to < for next-smaller; iterate right-to-left for previous-*.

Complexity: O(n) time, O(n) space — each element pushed and popped once.


Check yourself

Why does the monotonic stack achieve O(n) when the brute force is O(n²)?

In the brute force, each element can be compared against every other element — O(n) inner work per outer element, O(n²) total. In the monotonic stack, each element is pushed exactly once and popped at most once. Push + pop is O(1) amortized per element. Over all n elements: O(n) pushes + O(n) pops = O(n) total. The key insight: once an element is popped (its answer found), it is never touched again.

What does "monotonic" mean in "monotonic stack"? Which direction for next-greater vs next-smaller?

"Monotonic" means the values in the stack are always in a consistent order — either all increasing (bottom to top) or all decreasing (bottom to top). We enforce this by popping before pushing.

  • Next Greater → Decreasing stack (values decrease from bottom to top). The top is the smallest pending element, so a new larger value pops it.
  • Next Smaller → Increasing stack (values increase from bottom to top). The top is the largest pending element, so a new smaller value pops it.

A mnemonic: the answer defeats the top; "greater defeats smaller" → keep smaller on top (decreasing stack); "smaller defeats greater" → keep greater on top (increasing stack).

For a circular array (LC #503), how do you adapt the template?

Iterate indices 0 to 2n - 1, using i % n to wrap around. During the first pass (i < n), push indices onto the stack as normal. During the second pass (i ≥ n), only pop and answer — do not push again (to avoid double-counting). A common implementation: push only when i < n, but always run the while-pop loop.

for i in range(2 * n):
    while stack and nums[i % n] > nums[stack[-1]]:
        result[stack.pop()] = nums[i % n]
    if i < n:
        stack.append(i)

Practice problems

ProblemDifficultyWhat to noticeLink
Daily TemperaturesMediumNext greater; store index gapLC #739
Next Greater Element IEasyNGE for subset; hashmap + stack on full arrayLC #496
Next Greater Element IIMediumCircular → 2n iterationLC #503
Online Stock SpanMediumPrevious greater or equal; count spanLC #901
Sum of Subarray MinimumsMediumPrev/next smaller; count subarraysLC #907

Next up: Stack for Parentheses Matching — push opening brackets, pop on closing; a clean O(n) validity check with extensions for minimum removals and score computation.