Patterns/Stacks

Stacks

Top interview pattern
90 min read
Updated June 2026
What you'll learn
  • Identify when a problem requires LIFO access by asking whether the most recently deferred item is always the first one resolved
  • Build monotonic stacks where each element is pushed once and popped once, giving O(n) solutions to next-greater, span, and largest-rectangle problems
  • Evaluate expressions with operator precedence by deferring lower-priority operators on the stack and resolving higher-priority ones immediately
  • Derive a stack solution from scratch by deciding what gets deferred, what triggers resolution, what the stack stores, and when the answer is recorded

A stack holds items whose processing must wait for future context. Matching brackets, finding next-greater elements, evaluating expressions, and restoring nested state all reduce to the same structure: defer an item when it appears, then resolve it in reverse order when the resolving context arrives. The stack enforces that reverse order automatically.

What Is a Stack?

A stack exposes only its top element. Push adds to the top, pop removes from the top. The access order is LIFO: Last In, First Out.

The constraint is what makes stacks useful. Accessing elements in the middle requires first removing everything above them. This restriction maps directly to problems where the most recent item is always the first one needed.

Stack Operations (LIFO)

(empty)

Empty stack. Ready for operations

The stack starts empty. Elements enter and exit from the top only.

Step 1 of 9

In code, a stack provides three operations, all O(1):

Stack Operations
1stack = []
2
3# Push: add to top
4stack.append(1) # [1]
5stack.append(2) # [1, 2]
6stack.append(3) # [1, 2, 3]
7
8# Peek: look at top without removing
9top = stack[-1] # 3
10
11# Pop: remove and return top
12item = stack.pop() # returns 3, stack is now [1, 2]
13
14# Check if empty
15is_empty = len(stack) == 0 # or: not stack

What Stacks Actually Do

A stack avoids rescanning by holding items that cannot be processed yet. Without a stack, two common problem types require repeated work.

Why Counting Fails for Bracket Matching

A first attempt at bracket validation counts openers and closers. If counts match, it must be valid.

Counting Approach (Broken)
1def is_valid_broken(s):
2 count = 0
3 for char in s:
4 if char == '(':
5 count += 1
6 elif char == ')':
7 count -= 1
8 return count == 0 # Wrong!

This returns True for ")(", which is invalid. Tracking counts per bracket type still fails for "([)]", where counts are balanced but nesting is crossed.

Counting loses the sequence. The problem is not how many of each bracket type exist, but which specific opener the next closer should match. That depends on the order openers appeared. A data structure that preserves insertion order and returns the most recent item first solves this. That is a stack.

Why Brute Force Fails for Next Greater Element

For each element, find the first element to its right that is larger. The brute force approach scans rightward from every position.

Brute Force O(n²)
1def next_greater_brute(nums):
2 n = len(nums)
3 result = [-1] * n
4
5 for i in range(n):
6 for j in range(i + 1, n):
7 if nums[j] > nums[i]:
8 result[i] = nums[j]
9 break
10
11 return result

This is O(n²) because each element independently scans the remainder of the array. Elements that could share the same answer are checked separately. A stack avoids this by holding all elements that have not found their answer yet. When a large element arrives, every waiting element smaller than it gets resolved in one pass.

Both problems share a structure. Something is encountered now but cannot be fully processed until future context arrives. The stack holds the deferred items and returns them in the order they need to be resolved.

How the Stack Stores Unresolved Candidates

Every stack problem reduces to deferred processing. Something is encountered now, but it cannot be fully resolved until later. The stack holds items waiting to be completed.

Problem TypeWhat Gets DeferredWhat Triggers Resolution
Bracket matchingOpening bracketsMatching closing bracket
Next Greater ElementElements waiting for something largerA larger element arrives
Decode StringPartial results at each nesting levelClosing bracket triggers multiply
Expression evaluationOperands waiting for operatorOperator triggers computation

For monotonic stack problems specifically, the stack stores candidates that have not found their answer yet. When a new value arrives, it resolves older candidates that it dominates by popping them. Popped values are finalized. Values that remain on the stack survive because the current value could not resolve them.

Why is resolution always LIFO? The most recently deferred item is the first one that can be resolved, because anything between it and the current position would have resolved it already if it could. The fact that it is still on the stack means nothing sufficient has arrived since it was pushed. The next sufficient value resolves it first, and then checks the item below.

Key Insight
The Deferred Processing Pattern

An opener waits for its closer. A candidate waits for its next greater value. An operand waits for its operator. A nested context waits for its closing bracket. In each case, the most recently deferred item matches first because proper nesting, proximity, or precedence requires it. LIFO access is not arbitrary. It follows from the structure of the problem.

What the Stack Holds and Why

Each form of stack problem stores different things, but every stack has an invariant that must hold after each push and pop.

Matching

The stack holds unmatched openers. The top is always the most recent unmatched opener. When a closer arrives, it must match the top or the string is invalid. After processing all characters, an empty stack confirms every opener was matched.

Monotonic Stack

The stack holds unresolved candidates in sorted order (increasing or decreasing from bottom to top). The invariant is maintained by popping any element that the new arrival dominates. Values that survive are still waiting for their answer. Indices are usually stored instead of values because the answer must be recorded at a specific position in the result array.

Expression Evaluation

In RPN, the stack holds operands. Operators consume the top two operands immediately. In infix with precedence (Calculator II), the stack holds signed numbers. Higher-precedence operators (*, /) execute immediately by popping the top and computing. Lower-precedence operators (+, -) defer by pushing a signed number. At the end, summing the stack resolves all deferred operations.

State History

The stack holds snapshots of state at each nesting level. Pushing saves the current context before entering a new scope. Popping restores the parent context and attaches the resolved inner result. Each snapshot is a complete description of what was true before the nested scope began.

Matching and Pairing

This form applies when elements pair up in nested order. Signal words in the problem statement: valid, balanced, matching, nested.

Parentheses Matching

Input
(0
[1
{2
}3
]4
)5
Stack
(empty)
Ready to validate

Validate "([{}])" for bracket matching

Opening brackets push to stack; closing brackets must match the top.

Step 1 of 11

How It Works

When an opener appears, it cannot be validated yet because what comes next is unknown. But if a closer appears, it must match the most recent unmatched opener. Any other opener would create a crossing pattern like "([)]" which violates proper nesting. This "most recent first" requirement is exactly LIFO.

Push openers and wait. When a closer arrives, check if the stack top matches. If it does, pop and continue. If it doesn't (or the stack is empty), the string is invalid.

Matching Pattern Template
1def is_valid(s):
2 stack = []
3 pairs = {')': '(', ']': '[', '}': '{'} # closer -> opener
4
5 for char in s:
6 if char in '([{':
7 # OPENER: push and wait for matching closer
8 stack.append(char)
9 else:
10 # CLOSER: must match most recent opener
11 if not stack or stack[-1] != pairs[char]:
12 return False
13 stack.pop()
14
15 # All openers must have been matched
16 return len(stack) == 0

The template has three key decisions:

  1. What is an opener? For brackets: ([{}. For HTML tags: the tag name inside <tag>.
  2. What is a closer? The matching counterpart.
  3. How to verify match? Check that stack[-1] equals the expected opener.
Common Mistake
Empty Stack Access
The most common bug: accessing stack[-1] when the stack is empty. Input ")(" raises an IndexError at the first character. Always check if not stack before accessing the top.
Common Mistake
Forgetting Final Check
Returning True instead of len(stack) == 0 misses unmatched openers. Input "((" has no matching error during iteration but is still invalid.

Monotonic Stacks

This form applies when each element needs its nearest larger or smaller neighbor. Signal words: next greater, previous smaller, days until, span.

A monotonic stack maintains elements in sorted order: either always increasing from bottom to top, or always decreasing. This constraint is what makes nearest-element queries efficient.

Next Greater Element

Each element at index i waits for the first larger element to its right. Until that element arrives, the current element remains on the stack.

When a large element arrives, which waiting elements get answered? All the ones that are smaller than it. And the order in which they get answered follows LIFO: the most recent (smallest) waiting elements get popped first. When element C arrives, all elements smaller than C get popped, and C becomes their answer.

When element 4 arrives with stack [5, 3]: 4 > 3, so 3 is popped (its answer is 4). 4 < 5, so 4 pushes. Stack becomes [5, 4], still decreasing. The elements that would violate the ordering are exactly those whose answer has arrived. After removal, the invariant holds.

Key Insight
Why the Stack Stays Monotonic

The popping rule enforces the invariant. When a new element arrives, everything smaller than it gets popped. The remaining elements are all larger. The new element becomes the top, maintaining the decreasing invariant.

Monotonic Decreasing Stack: Next Greater Element

Input Array
30
11
42
13
54
95
26
67
Stack (decreasing: largest at bottom)
(empty)
Result (Next Greater Element)
Initialize

Building a decreasing monotonic stack (largest at bottom)

Each element waits on the stack until something greater arrives.

Step 1 of 17

Next Greater Element O(n)
1def next_greater_element(nums):
2 n = len(nums)
3 result = [-1] * n
4 stack = [] # stores indices of elements waiting for larger
5
6 for i in range(n):
7 # Pop all elements that found their answer
8 while stack and nums[i] > nums[stack[-1]]:
9 idx = stack.pop()
10 result[idx] = nums[i]
11
12 # Current element now waits
13 stack.append(i)
14
15 # Elements still in stack have no greater element
16 return result

Store indices in the stack, not values. Indices are needed to record answers in the result array. The value is always accessible via nums[stack[-1]].

Why Monotonic Stacks Are O(n)

Ignore the nested loops. Count what happens to each element. Each element enters the stack exactly once when first encountered. Each element exits the stack at most once when something larger arrives.

Each element is pushed once and popped at most once, so the total number of operations across the entire array is at most 2n.

The inner while loop executes at most n times total across all iterations, not n times per iteration. Any individual iteration of the for loop may trigger multiple pops, but the total number of pops across the entire run cannot exceed n.

Previous Smaller Element

Previous queries are similar to next queries but with a key difference: the answer is known at push time, not pop time. When pushing element i, first pop everything greater than or equal to it. Whatever remains on top of the stack after popping is the nearest element to the left that is strictly smaller.

The stack maintains increasing order from bottom to top. When pushing element i, first pop everything >= nums[i]. Whatever remains on top (if anything) is the nearest element to the left that is strictly smaller than nums[i]. It survived on the stack because nothing between it and i was small enough to displace it.

Previous Smaller Element
1def previous_smaller_element(nums):
2 n = len(nums)
3 result = [-1] * n
4 stack = [] # increasing stack
5
6 for i in range(n):
7 # Pop elements greater than or equal to current
8 while stack and nums[stack[-1]] >= nums[i]:
9 stack.pop()
10
11 # Stack top (if exists) is previous smaller
12 if stack:
13 result[i] = nums[stack[-1]]
14
15 stack.append(i)
16
17 return result

Monotonic Increasing Stack: Previous Smaller Element

Input Array
20
51
32
73
64
45
Stack (increasing: smallest at bottom)
(empty)
Result (Previous Smaller Element)
Initialize

Building an increasing monotonic stack (smallest at bottom)

When we push, the stack top tells us the previous smaller element.

Step 1 of 17

Query TypeStack OrderPop ConditionAnswer Location
Next GreaterDecreasingCurrent > TopAt pop time (current answers popped)
Next SmallerIncreasingCurrent < TopAt pop time
Previous GreaterDecreasingCurrent >= TopAt push time (stack top answers current)
Previous SmallerIncreasingCurrent <= TopAt push time

Largest Rectangle in Histogram

For each bar, we need to know how far left and right it can extend as the minimum height. A monotonic increasing stack gives us both boundaries. The width times the bar's height gives the area with that bar as the bottleneck.

Monotonic Stack Approach

Maintain an increasing stack. When a bar shorter than the stack top appears, pop and compute the area.

Width Formula

After popping the bar at index top, the rectangle extends from stack[-1]+1 to i-1. Width = i - stack[-1] - 1. Both boundaries are excluded.

Largest Rectangle in Histogram
1def largest_rectangle(heights):
2 heights.append(0) # Sentinel
3 stack = [-1] # Base sentinel
4 max_area = 0
5 for i, h in enumerate(heights):
6 while stack[-1] != -1 and heights[stack[-1]] >= h:
7 height = heights[stack.pop()]
8 width = i - stack[-1] - 1
9 max_area = max(max_area, height * width)
10 stack.append(i)
11 return max_area
Interview Tip
The Sentinel Trick
Without the sentinel, bars remaining on the stack after the loop have no shorter bar to trigger their popping. Their area would never be calculated. Appending a height of 0 at the end guarantees that every remaining bar is taller than the sentinel, so every remaining index gets popped and its area computed. The sentinel replaces the need for separate post-loop cleanup code.

Increasing vs Decreasing

The choice between increasing and decreasing stack depends on what triggers completion.

Ask: what are elements waiting for?

  • Waiting for something larger: Pop when larger arrives. This gives you a decreasing stack (largest at bottom).
  • Waiting for something smaller: Pop when smaller arrives. This gives you an increasing stack (smallest at bottom).
ProblemWaiting ForStack TypePop Condition
Next Greater ElementLarger elementDecreasingCurrent > Top
Daily TemperaturesWarmer (larger) dayDecreasingCurrent > Top
Stock SpanLarger price (to stop counting)DecreasingCurrent >= Top
Next Smaller ElementSmaller elementIncreasingCurrent < Top
Trapping Rain WaterTaller (larger) barDecreasingCurrent > Top
Key Insight
How to Determine the Stack Type
If popping when something larger arrives, elements that remain are larger than those that left. The remaining elements form a decreasing sequence.

If popping when something smaller arrives, elements that remain are smaller than those that left. The remaining elements form an increasing sequence.

The stack type follows from what survives. When a large element arrives and pops smaller ones, smaller elements leave and larger ones stay. The stack holds the large elements. Decreasing. When a small element arrives and pops larger ones, larger elements leave and smaller ones stay. The stack holds the small elements. Increasing.

To check your reasoning: trace one element through the algorithm. If it gets popped by something larger, you are building a decreasing stack. If it gets popped by something smaller, you are building an increasing stack.

Expression Evaluation

This form applies when operands must wait for their operators, and inner expressions must evaluate before outer ones. Signal words: evaluate, calculator, polish notation, infix.

Reverse Polish Notation (RPN)

In RPN, operators come after their operands: 3 4 + means 3 + 4. This eliminates the need for parentheses and precedence rules.

In RPN, the hard work is already done. The expression 3 4 2 * + means "multiply 4 and 2, then add 3." The multiplication appears first in the token stream because it should happen first. By the time you see the +, the * has already computed its result. Evaluating RPN requires one pass: scan left to right, push numbers, apply operators to the top two numbers. No lookahead, no precedence checks.

Evaluate Reverse Polish Notation
1def eval_rpn(tokens):
2 stack = []
3 ops = {
4 '+': lambda a, b: a + b,
5 '-': lambda a, b: a - b,
6 '*': lambda a, b: a * b,
7 '/': lambda a, b: int(a / b), # truncate toward zero
8 }
9
10 for token in tokens:
11 if token in ops:
12 b = stack.pop() # right operand (more recent)
13 a = stack.pop() # left operand (less recent)
14 stack.append(ops[token](a, b))
15 else:
16 stack.append(int(token))
17
18 return stack[0]
Common Mistake
Operand Order
Watch the operand order in RPN. For a b -, the result is a - b, not b - a. The first value you pop is the right operand, because it was pushed more recently. The second pop gives the left operand.

For "6 2 /": pop gives b=2, then a=6. Result is a/b = 6/2 = 3, not 2/6.

Reverse Polish Notation: ["3", "4", "2", "*", "+"]

Tokens (meaning: 3 + 4 * 2)
30
41
22
*3
+4
Stack (operands waiting for operators)
(empty)
Initialize

Initialize: stack = []

In RPN, operands push onto stack. Operators pop two operands, compute, push result.

Step 1 of 18

Calculator Problems

The Basic Calculator family handles infix notation with operator precedence:

  • Basic Calculator I: + - ( )
  • Basic Calculator II: + - * / (no parentheses)
  • Basic Calculator III: + - * / ( )

Basic Calculator II Strategy

Multiplication and division have higher precedence than addition and subtraction. In "3 + 4 * 2", 4 * 2 evaluates first, then 3 is added.

Addition and subtraction have lower precedence, so they must wait until all higher-precedence operations are resolved. Storing signed numbers on the stack for + or - defers the addition and subtraction. For * or /, immediate computation with the stack top is safe because no higher-precedence operation could intervene. At the end, summing the stack computes all the deferred additions and subtractions in order.

Track the previous operator. When a new operator appears, apply the previous one:

  • Previous was + or -: push the number (with sign) to stack
  • Previous was * or /: pop, compute, push result
Basic Calculator II
1def calculate(s: str) -> int:
2 stack = []
3 num = 0
4 sign = '+' # previous operator
5
6 for i, c in enumerate(s):
7 if c.isdigit():
8 num = num * 10 + int(c)
9
10 # Process when we hit operator or end of string
11 if c in '+-*/' or i == len(s) - 1:
12 if sign == '+':
13 stack.append(num)
14 elif sign == '-':
15 stack.append(-num)
16 elif sign == '*':
17 stack.append(stack.pop() * num)
18 elif sign == '/':
19 # Python division truncates toward negative infinity
20 # We need truncation toward zero
21 stack.append(int(stack.pop() / num))
22
23 sign = c
24 num = 0
25
26 return sum(stack)
Common Mistake
Division Truncation Is Language-Specific
In Python, int(a/b) truncates toward zero. a // b floors toward negative infinity. For -7 // 2, floor division gives -4, but the expected answer is -3. Use int(a/b) to match LeetCode's expected behavior.

Basic Calculator II: "3+4*2"

Expression: "3+4*2" = 3 + (4×2) = 11
30
+1
42
*3
24
Current State
num
0
prev sign
"+"
Stack (deferred values)
(empty)
Initialize

Initialize: stack=[], num=0, sign="+"

Track previous operator. Apply it when we see the NEXT operator (or end).

Step 1 of 19

State History

This form applies when processing creates nested contexts that must be restored in reverse order. Signal words: undo, back, nested structure, track history.

Each stack entry captures state at push time. Popping restores it. Undoing in arbitrary order would violate state dependencies. State B was created by modifying State A. The modification assumed A existed. To undo B, you need A's context. But if you tried to undo A first while B still exists, you would be removing the foundation that B was built on. Each state depends on the one before it. The dependency ordering is LIFO.

Decode String: Nested State

Problem: Given "3[a2[c]]", return "accaccacc".

When "[" appears, a nested context begins. Push the current state (partial result and multiplier) and reset the accumulator. When "]" appears, pop the saved state, multiply the inner result, and append.

Decode String
1def decode_string(s: str) -> str:
2 stack = [] # stores (previous_string, multiplier)
3 current_string = ""
4 current_num = 0
5
6 for char in s:
7 if char.isdigit():
8 current_num = current_num * 10 + int(char)
9 elif char == '[':
10 # Save state and reset
11 stack.append((current_string, current_num))
12 current_string = ""
13 current_num = 0
14 elif char == ']':
15 # Restore state and append multiplied result
16 prev_string, multiplier = stack.pop()
17 current_string = prev_string + current_string * multiplier
18 else:
19 current_string += char
20
21 return current_string

Decode String: Stack State History

Input: "3[a2[c]]"
30
[1
a2
23
[4
c5
]6
]7
Current State
string
""
num
0
Stack
(empty)
Initialize

Initialize: stack=[], current_string="", current_num=0

Stack stores (previous_string, multiplier) when entering nested brackets.

Step 1 of 18

Min Stack: O(1) getMin

Min Stack pairs each value with the running minimum at the time it was pushed. When an element is popped, the previous minimum is automatically the minimum stored with the element now on top. No rescanning is needed.

When Stacks Break

Stacks require LIFO access. When the next item to process is not the most recently added one, a stack is the wrong structure.

RequirementWhy Stack FailsUse Instead
Process in discovery order (FIFO)The oldest item needs to come out first, but the stack returns the newestQueue
Access both oldest and newestThe stack only exposes the topDeque
Random access by keyFinding an item in the middle requires popping everything above itHash map
Maintain sorted accessThe stack is ordered by insertion time, not by valueHeap or BST
Aggregate state across a rangeThe stack does not track a running sum, frequency, or count of a contiguous windowSliding window or prefix sum

The diagnostic question: when you retrieve a deferred item, is it the most recently deferred? If the answer is sometimes no, the problem does not have LIFO structure. BFS processes states in FIFO order. Sliding window queries need access to the entire range, not just the top. Top-k problems need the smallest or largest available element, not the most recent.

Stack vs Similar Patterns

ComparisonStackAlternativeWhen to Choose
Stack vs QueueLIFO: most recent item firstFIFO: oldest item firstUse queue when processing order must match discovery order (BFS, scheduling)
Monotonic Stack vs SortingPreserves position, gives nearest neighbor in O(n)Gives global order but loses original positionsUse sorting when global rank matters. Use monotonic stack when you need nearest-neighbor answers while preserving the left-to-right scan
Stack vs RecursionExplicit control over the deferred itemsImplicit call stack manages deferred framesUse explicit stack when recursion depth may exceed limits, when you need to inspect the stack contents, or when iterative control is required
Stack vs DequeAccess only the top (one end)Access both endsUse deque when you need to expire old items from the front while adding new items at the back (sliding window maximum)

Debugging Stack Code

Use these checks when your stack code fails hidden test cases.

Is the stack empty before accessing the top?

Empty Stack Bug
1# WRONG: crashes on ")"
2if stack[-1] == pairs[char]:
3
4# CORRECT: check first
5if stack and stack[-1] == pairs[char]:

Always check if stack before accessing stack[-1] or calling stack.pop().

Did you verify the stack is in the expected final state?

Final Check Bug
1# WRONG: returns True for "(("
2return True
3
4# CORRECT: check for unmatched openers
5return len(stack) == 0

After processing all input, verify the stack is empty (matching), fully resolved (monotonic), or contains the final answer (expression evaluation).

Is the width formula correct for histogram problems?

Width Bug
1# WRONG: off by one
2width = i - stack[-1]
3
4# CORRECT: exclude boundaries
5width = i - stack[-1] - 1

In histogram problems, both boundaries are excluded from the rectangle. The left boundary is the index below the popped element, and the right boundary is the current index.

Is the comparison strict or non-strict?

Comparison Bug
1# Next Greater: strictly greater
2while stack and nums[i] > nums[stack[-1]]:
3
4# Stock Span: greater or equal (counts consecutive)
5while stack and nums[i] >= nums[stack[-1]]:

Check whether the problem wants strict inequality (>) or non-strict (>=). This affects whether equal elements pop each other.

Is the operand order correct for non-commutative operations?

RPN Bug
1# WRONG: reversed operands
2a = stack.pop() # This is actually the right operand!
3b = stack.pop()
4result = a - b # Wrong for subtraction/division
5
6# CORRECT: b is right, a is left
7b = stack.pop() # Right operand (more recent)
8a = stack.pop() # Left operand (less recent)
9result = a - b # Correct order

First pop gives the right operand. Second pop gives the left operand. This matters for subtraction and division.

Deriving the Stack Solution

When a new problem involves deferred processing but you are not sure how to set up the stack, use these five questions to derive the structure.

StepQuestionBracket MatchingNext Greater ElementCalculator II
1What is being deferred?Opening bracketsElements waiting for largerSigned numbers for +/-
2What triggers resolution?Matching closer arrivesLarger element arrivesEnd of string or next operator
3Is resolution LIFO?Yes: most recent opener matches firstYes: smallest waiting element resolves firstYes: deferred sums resolve in order after all *// done
4What does the stack store?Characters (opener type)Indices (to write result at position)Signed numbers (value with sign)
5When is the answer recorded?Validity check on pop + final empty checkAt pop time: current value is the answer for popped indexAt the end: sum of stack is the final result

If step 3 is "no" (the most recently deferred item is not the first one resolved), a stack is the wrong data structure. Consider a queue, heap, or hash map instead.

For monotonic stack problems, add one more question: should the stack be increasing or decreasing? If elements are resolved when something larger arrives, the remaining elements are larger. Decreasing stack. If elements are resolved when something smaller arrives, the remaining elements are smaller. Increasing stack.

Practice Problems

Matching

ProblemDifficultyApproach
Valid ParenthesesEasyMost recent opener must match first. Check before accessing stack.
Minimum Remove to Make ValidMediumTrack indices of unmatched brackets. Two passes: find, then remove.
Longest Valid ParenthesesHardStack stores index of last unmatched. Length = current - stack top.
Basic CalculatorHardParentheses create nested contexts. Push state on "(", restore on ")".
Asteroid CollisionMediumStack holds right-moving asteroids. Resolve on collision.

Monotonic Stack

ProblemDifficultyApproach
Next Greater Element IEasyBuild answer map with decreasing stack first.
Daily TemperaturesMediumStore indices, not values. Answer is index difference.
Online Stock SpanMediumStack stores (price, span). Accumulate span when popping.
Maximal RectangleHardBuild histogram row by row. Apply largest rectangle to each.
Trapping Rain WaterHardDecreasing stack. Pop when taller found. Water bounded by min(walls).

Expression Evaluation

ProblemDifficultyApproach
Evaluate Reverse Polish NotationMediumFirst pop = right operand. Push result back.
Basic Calculator IIMediumProcess */ immediately. Defer +- by pushing signed numbers.
Basic Calculator IIIHardRecursion for parentheses or use two stacks.

State History

ProblemDifficultyApproach
Decode StringMediumPush (previous_string, multiplier) on "[". Restore on "]".
Design Browser HistoryMediumTwo stacks: back and forward. Move between them.

Reference Templates

Compact code shapes for recovery. Each template shows the core loop structure for one form.

Matching

Use when closers must match the most recent unmatched opener.

1stack = []
2for char in s:
3 if is_opener(char):
4 stack.append(char)
5 elif not stack or stack[-1] != expected_opener(char):
6 return False
7 else:
8 stack.pop()
9return len(stack) == 0

Next Greater Element (Monotonic Stack)

Use when elements wait for the next greater or smaller value.

1result = [-1] * n
2stack = []
3for i in range(n):
4 while stack and nums[i] > nums[stack[-1]]:
5 result[stack.pop()] = nums[i]
6 stack.append(i)
7return result

Previous Smaller Element

Use when the answer is known at push time after popping invalid candidates.

1result = [-1] * n
2stack = []
3for i in range(n):
4 while stack and nums[stack[-1]] >= nums[i]:
5 stack.pop()
6 if stack:
7 result[i] = nums[stack[-1]]
8 stack.append(i)
9return result

Largest Rectangle

Use when each bar needs the nearest smaller boundary on both sides.

1heights.append(0) # sentinel
2stack = [-1]
3max_area = 0
4for i, h in enumerate(heights):
5 while stack[-1] != -1 and heights[stack[-1]] >= h:
6 height = heights[stack.pop()]
7 width = i - stack[-1] - 1
8 max_area = max(max_area, height * width)
9 stack.append(i)
10return max_area

RPN Evaluation

Use when operators consume the most recent operands.

1stack = []
2for token in tokens:
3 if token in ops:
4 b, a = stack.pop(), stack.pop()
5 stack.append(ops[token](a, b))
6 else:
7 stack.append(int(token))
8return stack[0]

Calculator II

Use when multiplication and division resolve immediately while addition and subtraction are deferred.

1stack, num, sign = [], 0, '+'
2for i, c in enumerate(s):
3 if c.isdigit():
4 num = num * 10 + int(c)
5 if c in '+-*/' or i == len(s) - 1:
6 if sign == '+': stack.append(num)
7 elif sign == '-': stack.append(-num)
8 elif sign == '*': stack.append(stack.pop() * num)
9 elif sign == '/': stack.append(int(stack.pop() / num))
10 sign, num = c, 0
11return sum(stack)

Decode String

Use when nested contexts must be restored on closing brackets.

1stack, cur, num = [], "", 0
2for c in s:
3 if c.isdigit(): num = num * 10 + int(c)
4 elif c == '[':
5 stack.append((cur, num))
6 cur, num = "", 0
7 elif c == ']':
8 prev, mult = stack.pop()
9 cur = prev + cur * mult
10 else: cur += c
11return cur

Pattern Recap

Matching stacks hold unmatched openers. Each closer must match the most recent opener, or the structure is invalid. After processing, an empty stack confirms every opener was matched.

Monotonic stacks hold unresolved candidates in sorted order. A new value pops candidates it dominates, finalizing their answer. Values that survive have not found their answer yet. The answer is recorded at pop time for "next" queries and at push time for "previous" queries.

Expression stacks hold operands waiting for operators. In RPN, precedence is already encoded in token order, so evaluation is a single pass. In infix with precedence, lower-precedence operations are deferred while higher-precedence operations execute immediately.

State history stacks hold snapshots at each nesting level. Pushing saves the current context. Popping restores the parent context and attaches the resolved inner result.

Rebuilding the Pattern

When a stack problem feels unclear, start with what is being deferred. Something must be encountered now but processed later. Identify exactly what that something is: an opener, an element waiting for a larger neighbor, an operand, a partial result at a nesting level.

Then identify what triggers resolution. For matching, the trigger is a closer. For monotonic queries, the trigger is a value that dominates the waiting candidates. For expressions, the trigger is an operator. For nested state, the trigger is a closing bracket. The trigger tells you when to pop.

Confirm that the resolution order is LIFO. The most recently deferred item should always be the first one resolved. If this is not true, the problem does not have stack structure. If it is true, the stack will enforce the correct resolution order automatically.

Decide what to store. If the result must be placed at a specific position, store indices. If you need to restore a snapshot, store tuples of state. If you only need to compare values, store values directly.

Finally, decide when the answer is safe to record. In "next" monotonic queries, the answer is recorded when a candidate is popped. In "previous" queries, the answer is recorded when a new element is pushed and the stack top provides the answer. In matching, validity is determined both at each pop (does the closer match the opener?) and at the end (is the stack empty?). In expressions, the final answer is the value remaining on the stack after all tokens are processed.

Check Your Understanding

Use these questions to check whether you can reason through the pattern without looking at the template.

Ready to test it?

12 questions across all four stack patterns.

Practice this pattern

Apply the guide to complete interview problems with explanations and code.

Learn Stacks in a guided sequence

The Interview Course connects this pattern to its prerequisites, worked lessons, and progressively harder problems.

Explore the course