Coding patternsArrays and strings
Stacks and monotonic stacks
The stack and monotonic stack coding patterns for interviews. Matching brackets, evaluating expressions, and next greater element in one pass, with a step-through of daily temperatures, code in Java, Python, C#, Go and TypeScript, and 10 LeetCode problems.
A stack is the simplest structure there is: push on top, pop from the top. It earns its own pattern because a surprising number of problems have last in, first out hiding inside them. The bracket opened most recently must close first. The operands read most recently are the ones an operator applies to. The function called most recently returns first.
Its sharper form, the monotonic stack, solves a family of problems that look like they need O(n²) on n elements. For every element, find the next element to its right that is bigger (or smaller). A monotonic stack answers all of them in one pass.
How to spot it
Section titled “How to spot it”- Nesting or matching: brackets, tags, decoding
3[a2[c]], simplifying a file path. - Expressions: reverse Polish notation, a basic calculator.
- “Next greater”, “next smaller”, “previous greater”, or anything phrased as “how long until” or “how far back”: daily temperatures, stock span, car fleet.
- Areas bounded by heights: largest rectangle in a histogram, maximal rectangle, trapping rain water (which also has a two pointers solution).
Watch it run
Section titled “Watch it run”Daily temperatures: for each day, how many days until a warmer one? The stack holds days still waiting, and their temperatures decrease from bottom to top. A warm day pops every colder day on top, and each pop is an answer.
- answer = [0] * len(temperatures); stack = [] # days waiting
- for i, temperature in enumerate(temperatures):
- while stack and temperatures[stack[-1]] < temperature:
- waiting_day = stack.pop()
- answer[waiting_day] = i - waiting_day
- stack.append(i)
Day 0 (73°) waits on the stack for a warmer day.
Look at day 5 (72°). It answers day 4 and day 3, but stops at day 2 (75°), which stays waiting. Day 6 (76°) then answers both day 5 and day 2. The order on the stack is what makes it fast. Once a day finds something it can’t beat, it can stop, because everything below is warmer still.
The template
Section titled “The template”Matching brackets with a plain stack, and next warmer day with a monotonic one:
import java.util.ArrayDeque;import java.util.Arrays;import java.util.Deque;
public class Stacks { static boolean isValid(String text) { // The most recent unclosed bracket must close first: last in, first out. The stack // holds the openers still waiting for their partner. ArrayDeque, not the legacy Stack // class, which is synchronized and extends Vector. Deque<Character> stack = new ArrayDeque<>(); for (char bracket : text.toCharArray()) { switch (bracket) { case '(', '[', '{' -> stack.push(bracket); default -> { char open = bracket == ')' ? '(' : bracket == ']' ? '[' : '{'; if (stack.isEmpty() || stack.pop() != open) return false; } } } return stack.isEmpty(); // anything left never got closed }
static int[] dailyTemperatures(int[] temperatures) { // Monotonic stack: indexes of days still waiting for a warmer day, coldest on top. A // new day answers every colder day on top, then waits itself. Each index is pushed once // and popped at most once, so the nested loop is O(n) in total. int[] answer = new int[temperatures.length]; Deque<Integer> stack = new ArrayDeque<>(); for (int i = 0; i < temperatures.length; i++) { while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) { int waitingDay = stack.pop(); answer[waitingDay] = i - waitingDay; } stack.push(i); } return answer; // days still on the stack keep 0 }
public static void main(String[] args) { check(isValid("([]{})")); check(!isValid("([)]")); check(!isValid("((")); check(Arrays.equals( dailyTemperatures(new int[] {73, 74, 75, 71, 69, 72, 76, 73}), new int[] {1, 1, 4, 2, 1, 1, 0, 0})); System.out.println("ok: isValid and dailyTemperatures"); }
static void check(boolean ok) { if (!ok) throw new AssertionError(); }}def is_valid(text: str) -> bool: # The most recent unclosed bracket must close first: last in, first out. The stack holds # the openers still waiting for their partner. pairs = {")": "(", "]": "[", "}": "{"} stack: list[str] = [] for bracket in text: if bracket in pairs: if not stack or stack.pop() != pairs[bracket]: return False else: stack.append(bracket) return not stack # anything left never got closed
def daily_temperatures(temperatures: list[int]) -> list[int]: # Monotonic stack: indexes of days still waiting for a warmer day, coldest on top. A new # day answers every colder day on top, then waits itself. Each index is pushed once and # popped at most once, so the nested loop is O(n) in total. answer = [0] * len(temperatures) stack: list[int] = [] for i, temperature in enumerate(temperatures): while stack and temperatures[stack[-1]] < temperature: waiting_day = stack.pop() answer[waiting_day] = i - waiting_day stack.append(i) return answer # days still on the stack keep 0: no warmer day came
assert is_valid("([]{})")assert not is_valid("([)]")assert not is_valid("((")assert daily_temperatures([73, 74, 75, 71, 69, 72, 76, 73]) == [1, 1, 4, 2, 1, 1, 0, 0]print("ok: is_valid and daily_temperatures")using System;using System.Collections.Generic;using System.Linq;
static class Stacks{ public static bool IsValid(string text) { // The most recent unclosed bracket must close first: last in, first out. The stack // holds the openers still waiting for their partner. var stack = new Stack<char>(); foreach (char bracket in text) { if (bracket is '(' or '[' or '{') { stack.Push(bracket); continue; } char open = bracket switch { ')' => '(', ']' => '[', _ => '{' }; if (!stack.TryPop(out char top) || top != open) return false; } return stack.Count == 0; // anything left never got closed }
public static int[] DailyTemperatures(int[] temperatures) { // Monotonic stack: indexes of days still waiting for a warmer day, coldest on top. A // new day answers every colder day on top, then waits itself. Each index is pushed once // and popped at most once, so the nested loop is O(n) in total. var answer = new int[temperatures.Length]; var stack = new Stack<int>(); for (int i = 0; i < temperatures.Length; i++) { while (stack.Count > 0 && temperatures[stack.Peek()] < temperatures[i]) { int waitingDay = stack.Pop(); answer[waitingDay] = i - waitingDay; } stack.Push(i); } return answer; // days still on the stack keep 0 }
static void Main() { Check(IsValid("([]{})")); Check(!IsValid("([)]")); Check(!IsValid("((")); Check(DailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]) .SequenceEqual(new[] { 1, 1, 4, 2, 1, 1, 0, 0 })); Console.WriteLine("ok: IsValid and DailyTemperatures"); }
static void Check(bool ok) { if (!ok) throw new InvalidOperationException("check failed"); }}package main
import ( "fmt" "slices")
func isValid(text string) bool { // The most recent unclosed bracket must close first: last in, first out. A slice is the // stack: append pushes, and slicing off the last element pops. pairs := map[rune]rune{')': '(', ']': '[', '}': '{'} var stack []rune for _, bracket := range text { open, closing := pairs[bracket] if !closing { stack = append(stack, bracket) continue } if len(stack) == 0 || stack[len(stack)-1] != open { return false } stack = stack[:len(stack)-1] } return len(stack) == 0 // anything left never got closed}
func dailyTemperatures(temperatures []int) []int { // Monotonic stack: indexes of days still waiting for a warmer day, coldest on top. A new // day answers every colder day on top, then waits itself. Each index is pushed once and // popped at most once, so the nested loop is O(n) in total. answer := make([]int, len(temperatures)) var stack []int for i, temperature := range temperatures { for len(stack) > 0 && temperatures[stack[len(stack)-1]] < temperature { waitingDay := stack[len(stack)-1] stack = stack[:len(stack)-1] answer[waitingDay] = i - waitingDay } stack = append(stack, i) } return answer // days still on the stack keep 0}
func main() { check(isValid("([]{})")) check(!isValid("([)]")) check(!isValid("((")) temperatures := []int{73, 74, 75, 71, 69, 72, 76, 73} check(slices.Equal(dailyTemperatures(temperatures), []int{1, 1, 4, 2, 1, 1, 0, 0})) fmt.Println("ok: isValid and dailyTemperatures")}
func check(ok bool) { if !ok { panic("check failed") }}function isValid(text: string): boolean { // The most recent unclosed bracket must close first: last in, first out. An array is the // stack: push and pop work on its end in O(1). const pairs: Record<string, string> = { ")": "(", "]": "[", "}": "{" }; const stack: string[] = []; for (const bracket of text) { if (!(bracket in pairs)) stack.push(bracket); else if (stack.pop() !== pairs[bracket]) return false; // pop on empty gives undefined } return stack.length === 0; // anything left never got closed}
function dailyTemperatures(temperatures: number[]): number[] { // Monotonic stack: indexes of days still waiting for a warmer day, coldest on top. A new // day answers every colder day on top, then waits itself. Each index is pushed once and // popped at most once, so the nested loop is O(n) in total. const answer = new Array<number>(temperatures.length).fill(0); const stack: number[] = []; temperatures.forEach((temperature, i) => { while (stack.length && temperatures[stack[stack.length - 1]] < temperature) { const waitingDay = stack.pop()!; answer[waitingDay] = i - waitingDay; } stack.push(i); }); return answer; // days still on the stack keep 0}
const check = (ok: boolean) => { if (!ok) throw new Error("check failed");};check(isValid("([]{})"));check(!isValid("([)]"));check(!isValid("(("));check(dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]).join() === "1,1,4,2,1,1,0,0");console.log("ok: isValid and dailyTemperatures");The monotonic stack has one decision: which comparison pops?
| You want, for each element | Keep the stack | Pop while the top is |
|---|---|---|
| Next greater to the right | decreasing | smaller than the new element |
| Next smaller to the right | increasing | bigger than the new element |
| Previous greater to the left | decreasing | smaller or equal; the top after popping is the answer |
| Previous smaller to the left | increasing | bigger or equal; the top after popping is the answer |
For “next” answers, the answer is assigned when an item is popped. For “previous” answers, it’s read from whatever is on top after popping, right before the new item is pushed.
Variations
Section titled “Variations”Stack of state for nested decoding
Section titled “Stack of state for nested decoding”Decode string turns 3[a2[c]] into accaccacc. On [, push the string built so far and the
repeat count, and start fresh. On ], pop them and set the current string to
previous + count × current. Each level of nesting is one stack frame, exactly what recursion
would do, made explicit.
Min stack
Section titled “Min stack”Return the minimum in O(1) while pushing and popping. Store the minimum so far alongside every value, as a pair. When you pop, the minimum underneath comes back automatically. One stack of pairs is simpler and less bug-prone than two stacks kept in sync.
Largest rectangle in a histogram
Section titled “Largest rectangle in a histogram”Each bar extends left and right until it hits a shorter bar. An increasing stack finds both edges. When a bar is popped by a shorter one, the shorter bar is its right edge. The bar below it on the stack is its left edge. Push a height of 0 at the end, to flush everything out. It’s the hardest problem in this list, and the clearest proof that the pattern works.
Complexity
Section titled “Complexity”| Time | Extra memory | |
|---|---|---|
| Valid parentheses, RPN, decode string | O(n) | O(n) |
| Any monotonic stack problem | O(n) | O(n) |
| Min stack, per operation | O(1) | O(n) total |
For each day I need the next warmer day. Days waiting for an answer can sit on a stack in decreasing order, and each new day answers the colder ones on top. Every day is pushed and popped once, so it’s O(n).
Interview questions
Section titled “Interview questions”Interviewer asksThere's a while loop inside a for loop. Why is the monotonic stack O(n)?
Count pushes and pops instead of loop iterations. Each index is pushed exactly once, and can be popped at most once, because it’s never pushed again. The inner loop runs once per pop, so across the whole run it runs at most n times. Total work is O(n).
Interviewer asksWhy push indexes instead of temperatures?
The answer is a distance, i - waiting_day, so you need the position. You can always read the
value back with temperatures[waiting_day], but you can’t recover a position from a value when
values repeat. In histogram problems the index also gives the width.
Interviewer asksNext greater element in a circular array: how does the template change?
Loop over the array twice, from 0 to 2n - 1 for n numbers, using i % len(nums) as the index, and
only push during the first pass. The second pass lets elements near the end find answers near the
start. Elements still on the stack after both passes have no greater element.
Practice
Section titled “Practice”10 problems on LeetCode: 2 easy, 7 medium, 1 hard
- Valid Parentheses(opens LeetCode in a new tab)Easy
The most recent opener must close first.
- Next Greater Element I(opens LeetCode in a new tab)Easy
A decreasing stack pops each element when its answer arrives.
- Min Stack(opens LeetCode in a new tab)Medium
Store the minimum so far alongside each value.
- Evaluate Reverse Polish Notation(opens LeetCode in a new tab)Medium
Operands wait on the stack until their operator arrives.
- Daily Temperatures(opens LeetCode in a new tab)Medium
Keep waiting days on a stack; a warmer day answers all the colder ones.
- Asteroid Collision(opens LeetCode in a new tab)Medium
Only a left-mover meeting the top right-mover can collide.
- Decode String(opens LeetCode in a new tab)Medium
Push the partial string and count on "[" and pop on "]".
- Online Stock Span(opens LeetCode in a new tab)Medium
Pop smaller prices and absorb their spans into the new one.
- Car Fleet(opens LeetCode in a new tab)Medium
Sorted by position, a car that would arrive sooner joins the fleet ahead.
- Largest Rectangle in Histogram(opens LeetCode in a new tab)Hard
An increasing stack finds where each bar stops extending, on both sides.
Ticks are saved in this browser only. Clear your site data and they're gone.
- Use a stack when the most recent unfinished item must be dealt with first: brackets, expressions, nesting.
- Use a monotonic stack for next or previous greater or smaller, spans and histogram areas.
- The rule: a new item pops everything it beats, and each pop is an answer.
- Cost: O(n), because every item is pushed and popped at most once.
- Watch for: popping an empty stack, and leftover items at the end (unclosed brackets, or elements with no answer).
The designbeyondcode digest, every Saturday
New deep dives, interview tips and site news in one short email. Free. Leave with one click.
We never sell or share your email. We only use it to send this newsletter.