Stack — The Complete Guide
Master the stack from first principles. Learn to recognize when LIFO ordering is the key insight, build monotonic stacks for next-greater problems, and confidently tackle every stack-based interview question.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
A stack is the simplest data structure with a powerful constraint: you can only add and remove from the top. This Last-In, First-Out (LIFO) behavior sounds limiting, but it's exactly what you need whenever a problem involves matching things in reverse order, tracking "the most recent" item, or processing nested structures from the inside out.
Think about what happens when you type in a text editor and press Ctrl+Z. The most recent action gets undone first — that's a stack. When your browser tracks the pages you've visited and you hit the back button, it pops the most recent page — that's a stack. When a programming language executes nested function calls, it pushes each call onto a call stack and pops them as they return.
In coding interviews, stacks appear in three major families: (1) bracket matching and expression evaluation, (2) monotonic stacks for "next greater/smaller" problems, and (3) simulation of recursion or undo operations. Once you see the LIFO pattern in a problem, the solution almost writes itself.
Analogies to Build Intuition
Stack of Pancakes
You can only eat the top pancake. To reach the one at the bottom, you must eat every pancake above it first. That's LIFO — the last pancake placed on the stack is the first one you eat. In code, the last element pushed is the first one popped.
Boxes in a Narrow Hallway
Imagine stacking boxes in a narrow hallway where you can only access the front. To get to a box in the back, you must move every box in front of it first. This is why stacks are perfect for 'undo' operations — you always reverse the most recent action first.
Matching Parentheses by Hand
When you read '({[]})', you mentally 'remember' each opening bracket and match it with the nearest closing one. Your brain is using a stack — you push '(', '{', '[', then pop '[' when you see ']', pop '{' when you see '}', and pop '(' when you see ')'. The most recent opener matches the first closer.
What kind of problems does it solve?
Stacks are your go-to when:
- You need to match or validate nested structures (parentheses, HTML tags, nested expressions)
- You need to evaluate or parse expressions (postfix, infix with operator precedence)
- You need to find the next greater or next smaller element for each position
- You need to track a running state that can be "undone" (backspace, undo, simplify path)
- You need to simulate recursion iteratively (DFS, tree traversal without recursion)
- You need to compute areas or spans based on boundaries (largest rectangle in histogram, stock span)
The core insight
A stack gives you O(1) access to the most recent unprocessed item. Whenever a problem requires you to "go back to the last thing" or "match the nearest opener," a stack is the natural choice. The LIFO constraint isn't a limitation — it's the feature.
Pattern Recognition
Recognizing when a stack is the right tool is the most important skill. Here are the signals to look for and the traps to avoid.
Keywords & Signals in the Problem Statement
Use Stack when you see...
- ✅"Valid parentheses" or bracket matching
- ✅"Next greater element" or "next smaller element"
- ✅"Evaluate expression" (postfix, infix, calculator)
- ✅"Decode string" with nested brackets like "3[a2[c]]"
- ✅"Simplify path" or "remove duplicates" in sequence
- ✅"Backspace string compare" — undo the last character
- ✅"Largest rectangle in histogram" — boundary-based area
- ✅"Daily temperatures" — how many days until warmer
- ✅"Min stack" or "max stack" — track extremes alongside values
- ✅Any problem with nested or recursive structure needing LIFO
Don't use Stack when...
- ❌You need FIFO (first-in, first-out) order — use a Queue or Deque
- ❌You need to find the Kth largest/smallest — use a Heap
- ❌The problem is about sliding window min/max — use a Monotonic Deque
- ❌You need random access to elements by index — use an Array or HashMap
- ❌The problem involves sorting or ordering — Sorting or Heap fits better
- ❌You need BFS traversal — use a Queue, not a Stack
Stack vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Stack (LIFO) | Matching in reverse order, nested structures, undo operations | Last-in, first-out — access only the most recent item |
| Queue (FIFO) | BFS, level-order traversal, task scheduling | First-in, first-out — process in arrival order |
| Monotonic Stack | Next greater/smaller element, histogram areas, stock span | Stack variant that maintains sorted order — elements are popped when they violate the monotonic property |
| Monotonic Deque | Sliding window min/max | Double-ended queue maintaining monotonic order within a window |
| Heap / Priority Queue | Top-K elements, streaming min/max, merge K sorted lists | Gives min/max in O(log n) — not LIFO, but priority-based |
| Recursion (Call Stack) | Tree/graph DFS, divide and conquer | Implicit stack via function calls — can always be converted to explicit stack |
The stack question to always ask
When you see a problem involving sequences, ask: "Do I need to process things in reverse order or match the most recent unmatched item?" If yes, reach for a stack. The LIFO property is the signal — if the most recent item is always the most relevant, a stack is your answer.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using a concrete example: Daily Temperatures — given an array of daily temperatures, return an array where each element tells you how many days you have to wait until a warmer temperature.
Brute Force — Check Every Future Day
For each day, scan forward through all future days to find the first warmer one. If temperatures are decreasing, you scan the entire remaining array for every element.
function dailyTemperaturesBrute(temps: number[]): number[] { const n = temps.length; const result = new Array(n).fill(0); for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { if (temps[j] > temps[i]) { result[i] = j - i; break; } } } return result; } // Problem: For each of n elements, we might scan up to n elements. // Worst case (descending array): O(n²).
Optimal — Monotonic Stack (Decreasing)
The key insight: instead of looking forward from each day, we process days left-to-right and use a stack to remember days that haven't found their answer yet. When we encounter a warmer day, we pop all cooler days from the stack and record their answers.
function dailyTemperatures(temps: number[]): number[] { const n = temps.length; const result = new Array(n).fill(0); const stack: number[] = []; // stores indices for (let i = 0; i < n; i++) { // Pop all days that are cooler than today while (stack.length > 0 && temps[i] > temps[stack[stack.length - 1]]) { const prevDay = stack.pop()!; result[prevDay] = i - prevDay; } // Push today — it hasn't found a warmer day yet stack.push(i); } return result; } // Each index is pushed once and popped at most once. // Total operations: O(2n) = O(n).
Why Does the Optimization Work?
The stack remembers 'waiting' elements
Each element on the stack is waiting for a larger value to appear. Instead of each element scanning forward independently (O(n) per element), the stack lets a single new element resolve multiple waiting elements at once.
Each element is pushed and popped exactly once
An element enters the stack when we first see it and leaves when a larger element arrives. That's at most 2 operations per element — O(2n) = O(n) total, regardless of the input pattern.
The stack maintains a decreasing order
The stack always holds indices in decreasing order of their temperatures. When a new temperature breaks this order, we pop until the order is restored. This 'monotonic' property is what makes the algorithm efficient — we never re-examine elements.
The thinking process matters more than the code
In interviews, walk through this progression: "The brute force checks every future day for each element — O(n²). But I notice that I'm repeatedly scanning for the 'next greater element.' If I use a stack to track elements waiting for their answer, each element is processed at most twice (push + pop), giving O(n)." This shows the interviewer you understand WHY the stack helps.
Core Templates
Stack-based problems follow a few recurring templates. Memorize these structures — most problems are variations of one of them.
Template 1: Bracket Matching / Validation
Push opening tokens onto the stack. When you encounter a closing token, pop and verify it matches. Valid if the stack is empty at the end.
function isValid(s: string): boolean { const stack: string[] = []; const pairs: Record<string, string> = { ")": "(", "]": "[", "}": "{" }; for (const ch of s) { if (ch in pairs) { // Closing bracket — pop and check match if (stack.length === 0 || stack.pop() !== pairs[ch]) { return false; } } else { // Opening bracket — push stack.push(ch); } } return stack.length === 0; // no unmatched openers } // When to use: valid parentheses, valid tag nesting, // balanced brackets, matching delimiters
Template 2: Monotonic Stack (Next Greater / Smaller)
Maintain a stack where elements are in monotonic order (all increasing or all decreasing). When a new element violates the order, pop elements and record their answers.
function nextGreaterElement(nums: number[]): number[] { const n = nums.length; const result = new Array(n).fill(-1); const stack: number[] = []; // stores indices for (let i = 0; i < n; i++) { // Pop elements smaller than current — current is their "next greater" while (stack.length > 0 && nums[i] > nums[stack[stack.length - 1]]) { const idx = stack.pop()!; result[idx] = nums[i]; } stack.push(i); } return result; } // Variation: for "next smaller", flip the comparison to < // Variation: for "previous greater", iterate right-to-left // When to use: daily temperatures, next greater element, // stock span, largest rectangle in histogram
Template 3: Expression Evaluation
Use one stack for numbers and (optionally) one for operators. Process tokens left-to-right, applying operators based on precedence. Parentheses trigger recursive evaluation.
function calculate(s: string): number { const stack: number[] = []; let num = 0; let sign = 1; let result = 0; for (const ch of s) { if (ch >= '0' && ch <= '9') { num = num * 10 + Number(ch); } else if (ch === '+' || ch === '-') { result += sign * num; num = 0; sign = ch === '+' ? 1 : -1; } else if (ch === '(') { // Save current state and start fresh stack.push(result); stack.push(sign); result = 0; sign = 1; } else if (ch === ')') { result += sign * num; num = 0; // Restore previous state result *= stack.pop()!; // sign before '(' result += stack.pop()!; // result before '(' } } return result + sign * num; } // When to use: basic calculator I/II/III, // evaluate reverse polish notation, decode string
Template 4: Stack for Undo / State Tracking
Push state onto the stack as you process. When you need to "undo" or "go back," pop the most recent state. Used for path simplification, backspace operations, and string manipulation.
function simplifyPath(path: string): string { const stack: string[] = []; const parts = path.split("/"); for (const part of parts) { if (part === "..") { stack.pop(); // go up one directory } else if (part !== "" && part !== ".") { stack.push(part); // enter directory } // skip empty strings and "." (current directory) } return "/" + stack.join("/"); } // When to use: simplify path, backspace string compare, // remove all adjacent duplicates, asteroid collision
Which template to pick?
Ask yourself: (1) Am I matching or validating nested tokens? → Template 1. (2) Am I finding the next greater/smaller element? → Template 2. (3) Am I evaluating a mathematical expression? → Template 3. (4) Am I undoing operations or simplifying a sequence? → Template 4.
Variations & Sub-Patterns
The stack isn't a single trick — it's a family of techniques. Here are the most common variations and how the approach changes for each.
| Variation | Stack Strategy | Classic Example |
|---|---|---|
| Bracket Matching | Push openers, pop on closers, verify match | Valid Parentheses, Remove Invalid Parentheses |
| Monotonic Stack (Decreasing) | Pop smaller elements when a larger one arrives | Daily Temperatures, Next Greater Element |
| Monotonic Stack (Increasing) | Pop larger elements when a smaller one arrives | Largest Rectangle in Histogram, Trapping Rain Water |
| Expression Evaluation | Number stack + operator handling with precedence | Basic Calculator, Evaluate RPN |
| String Decoding / Building | Push partial results, pop and combine on closing delimiter | Decode String, Remove Duplicate Letters |
| Undo / Simplification | Push valid items, pop to undo or backtrack | Simplify Path, Backspace String Compare |
| Min/Max Stack | Auxiliary stack tracking running min/max alongside values | Min Stack, Max Stack |
| Simulation (Collision / Merge) | Push items, pop when they interact with the new item | Asteroid Collision, Remove All Adjacent Duplicates |
Problems mentioned above
Deep Dive: Monotonic Stack — Largest Rectangle in Histogram
Given an array of bar heights, find the area of the largest rectangle that fits entirely under the histogram. This is the hardest and most important monotonic stack problem — it combines "next smaller on left" and "next smaller on right" into one pass.
function largestRectangleArea(heights: number[]): number { const stack: number[] = []; // stores indices let maxArea = 0; const n = heights.length; for (let i = 0; i <= n; i++) { // Use 0 as sentinel for the final cleanup pass const currHeight = i === n ? 0 : heights[i]; while (stack.length > 0 && currHeight < heights[stack[stack.length - 1]]) { const height = heights[stack.pop()!]; // Width: from the element below in stack to current index const width = stack.length === 0 ? i : i - stack[stack.length - 1] - 1; maxArea = Math.max(maxArea, height * width); } stack.push(i); } return maxArea; } // Time: O(n) — each bar is pushed and popped at most once // Space: O(n) — stack holds at most n indices // Key: when a bar is popped, the current index is its "next smaller on right" // and the new stack top is its "next smaller on left".
Deep Dive: String Decoding — Decode String
Given an encoded string like "3[a2[c]]", decode it to "accaccacc". The nesting means you need to save your current state when entering a bracket and restore it when leaving.
function decodeString(s: string): string { const countStack: number[] = []; const stringStack: string[] = []; let currentStr = ""; let k = 0; for (const ch of s) { if (ch >= '0' && ch <= '9') { k = k * 10 + Number(ch); } else if (ch === '[') { // Save current state and start fresh countStack.push(k); stringStack.push(currentStr); currentStr = ""; k = 0; } else if (ch === ']') { // Pop and repeat const repeatCount = countStack.pop()!; const prevStr = stringStack.pop()!; currentStr = prevStr + currentStr.repeat(repeatCount); } else { currentStr += ch; } } return currentStr; } // Time: O(n × maxRepeat) — building the decoded string // Space: O(n) — stack depth equals nesting depth // Key: '[' saves state (push), ']' restores and combines (pop).
Deep Dive: Simulation — Asteroid Collision
Asteroids move in a row. Positive = right, negative = left. When two collide (right-moving meets left-moving), the smaller one explodes. If equal, both explode. The stack simulates this perfectly.
function asteroidCollision(asteroids: number[]): number[] { const stack: number[] = []; for (const ast of asteroids) { let alive = true; while (alive && ast < 0 && stack.length > 0 && stack[stack.length - 1] > 0) { const top = stack[stack.length - 1]; if (top < Math.abs(ast)) { stack.pop(); // top asteroid explodes, keep checking } else if (top === Math.abs(ast)) { stack.pop(); // both explode alive = false; } else { alive = false; // incoming asteroid explodes } } if (alive) stack.push(ast); } return stack; } // Time: O(n) — each asteroid is pushed/popped at most once // Space: O(n) — stack holds surviving asteroids // Key: only right-moving (positive) vs left-moving (negative) collide. // Same direction or left-then-right never collide.
Visual Walkthrough
Let's trace through three classic stack problems step by step to see the pattern in action.
Valid Parentheses — Step by Step
Input: "({[]})" Step 1: ch = '(' → opening bracket → push stack = ['('] Step 2: ch = '{' → opening bracket → push stack = ['(', '{'] Step 3: ch = '[' → opening bracket → push stack = ['(', '{', '['] Step 4: ch = ']' → closing bracket → pop '[', check: pairs[']'] = '[' ✓ stack = ['(', '{'] Step 5: ch = '}' → closing bracket → pop '{', check: pairs['}'] = '{' ✓ stack = ['('] Step 6: ch = ')' → closing bracket → pop '(', check: pairs[')'] = '(' ✓ stack = [] Stack is empty → VALID ✓ Key: Each closer matches the MOST RECENT opener — that's LIFO. The stack naturally handles arbitrary nesting depth.
Daily Temperatures (Monotonic Stack) — Step by Step
Input: temps = [73, 74, 75, 71, 69, 72, 76, 73] result = [0, 0, 0, 0, 0, 0, 0, 0] stack = [] (stores indices) i=0: temp=73, stack empty → push 0 stack = [0] i=1: temp=74 > temps[0]=73 → pop 0, result[0] = 1-0 = 1 stack empty, push 1 stack = [1] result = [1,0,0,0,0,0,0,0] i=2: temp=75 > temps[1]=74 → pop 1, result[1] = 2-1 = 1 stack empty, push 2 stack = [2] result = [1,1,0,0,0,0,0,0] i=3: temp=71 < temps[2]=75 → no pop, push 3 stack = [2,3] i=4: temp=69 < temps[3]=71 → no pop, push 4 stack = [2,3,4] i=5: temp=72 > temps[4]=69 → pop 4, result[4] = 5-4 = 1 temp=72 > temps[3]=71 → pop 3, result[3] = 5-3 = 2 temp=72 < temps[2]=75 → stop, push 5 stack = [2,5] result = [1,1,0,2,1,0,0,0] i=6: temp=76 > temps[5]=72 → pop 5, result[5] = 6-5 = 1 temp=76 > temps[2]=75 → pop 2, result[2] = 6-2 = 4 stack empty, push 6 stack = [6] result = [1,1,4,2,1,1,0,0] i=7: temp=73 < temps[6]=76 → no pop, push 7 stack = [6,7] result = [1,1,4,2,1,1,0,0] Done. Indices 6,7 remain on stack → no warmer day → result stays 0. Answer: [1, 1, 4, 2, 1, 1, 0, 0] Key: The stack always holds indices in DECREASING temperature order. When a warmer day arrives, it resolves all cooler days waiting on the stack.
Largest Rectangle in Histogram — Step by Step
Input: heights = [2, 1, 5, 6, 2, 3] stack = [], maxArea = 0 i=0: h=2, stack empty → push 0 stack = [0] i=1: h=1 < heights[0]=2 → pop 0 height=2, width=1 (stack empty, so width=i=1) area = 2×1 = 2, maxArea = 2 push 1 stack = [1] i=2: h=5 > heights[1]=1 → push 2 stack = [1,2] i=3: h=6 > heights[2]=5 → push 3 stack = [1,2,3] i=4: h=2 < heights[3]=6 → pop 3 height=6, width = 4-2-1 = 1, area = 6, maxArea = 6 h=2 < heights[2]=5 → pop 2 height=5, width = 4-1-1 = 2, area = 10, maxArea = 10 h=2 ≥ heights[1]=1 → stop, push 4 stack = [1,4] i=5: h=3 > heights[4]=2 → push 5 stack = [1,4,5] i=6 (sentinel h=0): pop 5 height=3, width = 6-4-1 = 1, area = 3 pop 4: height=2, width = 6-1-1 = 4, area = 8 pop 1: height=1, width = 6 (stack empty), area = 6 maxArea = 10 Answer: 10 (the 5×2 rectangle spanning bars at index 2 and 3) Key: When a bar is popped, its left boundary is the new stack top and its right boundary is the current index.
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of stack-based problem solving. Solve them in order.
LC 20. Valid Parentheses
Why this pattern:
Bracket matching — the foundational stack problem. Push opening brackets, pop on closing brackets, verify the match. Valid if the stack is empty at the end.
Key idea: Map each closer to its opener. For each character: push if opener, pop and compare if closer. Any mismatch, premature empty stack, or leftover items means invalid. This is the 'hello world' of stacks.
LC 155. Min Stack
Why this pattern:
Auxiliary stack tracking the running minimum. Every push/pop keeps both stacks in sync so getMin() is always O(1). Tests your ability to augment a basic stack with extra state.
Key idea: Maintain a second stack where each entry is the minimum value at that depth. When pushing, push min(newValue, currentMin) onto the min stack. When popping, pop both. The top of the min stack is always the current minimum.
LC 150. Evaluate Reverse Polish Notation
Why this pattern:
Expression evaluation with a single stack. Push numbers, pop two operands when you hit an operator, compute, and push the result. The final stack item is the answer.
Key idea: Iterate through tokens. Numbers get pushed. Operators pop two values — watch the order: second popped is the left operand, first popped is the right. Push the result. Handles +, -, *, / with integer division truncating toward zero.
LC 739. Daily Temperatures
Why this pattern:
Monotonic decreasing stack — the classic 'next greater element' problem. The stack holds indices of days waiting for a warmer temperature. When a warmer day arrives, pop and record the distance.
Key idea: Push indices onto the stack. For each new temperature, pop all indices where the temperature is lower — the distance (current index - popped index) is the answer for that day. Each index is pushed and popped at most once → O(n).
LC 394. Decode String
Why this pattern:
Nested structure with state saving. Use two stacks (or one stack of pairs): one for repeat counts, one for the string built so far. '[' saves state, ']' restores and combines.
Key idea: On '[': push current count and current string, reset both. On ']': pop the count and previous string, combine as prevString + currentString.repeat(count). On digit: build the number. On letter: append to current string.
LC 84. Largest Rectangle in Histogram
Why this pattern:
Monotonic increasing stack — the most important hard stack problem. When a shorter bar arrives, pop taller bars and calculate the rectangle they could form. The stack top gives the left boundary, current index gives the right boundary.
Key idea: Push indices in increasing height order. When a shorter bar arrives, pop and compute area = height × width, where width = currentIndex - stackTop - 1 (or currentIndex if stack is empty). Use a sentinel (height 0) at the end to flush remaining bars.
LC 42. Trapping Rain Water
Why this pattern:
Monotonic stack approach (one of three valid approaches). When a taller bar arrives, pop shorter bars and calculate the water trapped between the popped bar, the new stack top (left boundary), and the current bar (right boundary).
Key idea: Maintain a decreasing stack of indices. When heights[i] > heights[stack.top], pop and compute water: width = i - stack.top - 1, height = min(heights[i], heights[newTop]) - heights[popped]. Accumulate the area. Also solvable with two pointers or prefix max arrays.
Practice strategy
For each problem: (1) Identify which stack template applies before coding. (2) Ask "what am I pushing and when am I popping?" (3) After solving, write one sentence: "The stack holds [what] and pops when [condition]." This builds your pattern recognition muscle.
Common Mistakes
These are the traps that catch people on stack-based problems. Learn them here so you don't learn them in an interview.
Popping from an empty stack
You call stack.pop() without checking if the stack is empty first. This causes runtime errors or returns undefined, leading to subtle bugs in bracket matching and expression evaluation.
✅Always guard pops with a length check: if (stack.length === 0) return false (for validation) or handle the empty case explicitly. In monotonic stack problems, the empty stack often means 'no left boundary' — handle it as a special case for width calculation.
Storing values instead of indices in monotonic stack
You push the actual values onto the stack instead of indices. When you need to calculate distances or widths (Daily Temperatures, Histogram), you can't compute them without indices.
✅Almost always push indices, not values. You can always look up the value with arr[stack[stack.length - 1]]. Indices give you both the value AND the position, which you need for distance/width calculations.
Forgetting to flush the stack at the end
In monotonic stack problems like Largest Rectangle in Histogram, elements remaining on the stack after the loop still need to be processed. Forgetting this misses rectangles that extend to the end of the array.
✅Add a sentinel value at the end. For histogram, append a bar of height 0 (or loop to i = n with a virtual height of 0). This forces all remaining elements to be popped and processed. The sentinel is the cleanest approach.
Wrong operand order in expression evaluation
In Evaluate RPN, you pop two operands for subtraction or division but use them in the wrong order. pop() gives the RIGHT operand first, then the LEFT. '6 3 /' should be 6/3 = 2, not 3/6 = 0.
✅Always assign: right = stack.pop(), left = stack.pop(). Then compute left OP right. This is the most common bug in calculator problems — test with a non-commutative operation like subtraction or division.
Confusing monotonic increasing vs decreasing
You use a decreasing stack when you need increasing (or vice versa). 'Next greater element' needs a decreasing stack (pop smaller elements). 'Largest rectangle' needs an increasing stack (pop taller bars).
✅Think about what triggers a pop: 'Next greater' → pop when current is GREATER than top (stack holds decreasing values). 'Next smaller' / 'histogram' → pop when current is SMALLER than top (stack holds increasing values). The pop condition defines the monotonic direction.
Incorrect width calculation in histogram
When computing the rectangle width after popping, you use 'i - poppedIndex' instead of 'i - stack.top - 1'. The width extends from the new stack top (left boundary) to the current index (right boundary), not from the popped bar itself.
✅Width = i - stack[stack.length - 1] - 1 when the stack is non-empty, or width = i when the stack is empty (the bar extends all the way to the left). Draw it out: the popped bar's rectangle spans from its left boundary (new stack top + 1) to its right boundary (i - 1).
Interview Strategy
Knowing when and how to use a stack is half the battle. Here's how to present it in an interview setting to maximize your score.
The 5-Step Interview Flow
Clarify & Identify the LIFO Signal
'So I need to find the next warmer day for each day — that means I'm looking for the next greater element. This is a classic monotonic stack problem.' — Name the pattern early to show confidence.
State the Brute Force
'The brute force would check every future day for each element — O(n²). But I notice that elements waiting for their answer can be tracked on a stack, and a single warmer day can resolve multiple waiting elements at once.' — Show you understand the baseline.
Explain What the Stack Holds and When You Pop
'The stack holds indices of days in decreasing temperature order. When I encounter a warmer day, I pop all cooler days and record their answers. Each element is pushed and popped at most once, so it's O(n).' — This is the key insight interviewers want to hear.
Code It Clean
Use descriptive variable names (prevDay, currTemp, not i, j). Comment what the stack holds. Keep the while-pop loop clean and the push at the end obvious. Handle edge cases (empty stack, sentinel values) explicitly.
Test & Analyze
Trace through a small example: 'For [73, 74, 75, 71, 69, 72, 76, 73]: push 0, then 74 > 73 so pop 0 (answer = 1), push 1...' Walk through 3-4 steps, then state: 'O(n) time because each element is pushed and popped at most once. O(n) space for the stack.'
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "Can you do it with O(1) space?" | For some problems (like Backspace String Compare), yes — use two pointers iterating from the end. For monotonic stack problems, O(n) space is generally necessary. Mention the tradeoff. |
| "What if the array is circular?" | Process the array twice (2n iterations) or use modular indexing: i % n. This handles wrap-around for problems like Next Greater Element II (LC #503). The stack logic stays the same. |
| "Can you solve Trapping Rain Water without a stack?" | Yes — two pointers (O(1) space) or prefix max arrays (O(n) space). The stack approach is O(n) time and space. Mention all three and let the interviewer pick which to code. |
| "What's the difference between monotonic stack and monotonic deque?" | A monotonic stack finds the next greater/smaller element globally. A monotonic deque maintains min/max within a sliding window of fixed size. Deque removes from both ends; stack only from one end. |
| "How would you handle operator precedence in a calculator?" | Use two stacks (numbers + operators) or process * and / immediately while deferring + and -. For full precedence with parentheses, use the Shunting Yard algorithm or recursive descent. |
| "Why not use recursion instead of a stack?" | Recursion uses the call stack implicitly — same idea, but with function call overhead and potential stack overflow for deep inputs. An explicit stack gives you control over memory and avoids recursion limits. |
The meta-skill
Interviewers love stack problems because they test pattern recognition and the ability to explain invariants. The key sentence to say is: "The stack holds [what] in [monotonic/LIFO] order, and I pop when [condition]. Each element is pushed and popped at most once, so the total time is O(n)." This single sentence covers the algorithm, the invariant, and the complexity.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: LIFO — the most recent item is always the most relevant. Push to remember, pop to resolve.
When to use: Ask: 'Do I need to match the most recent unmatched item or process in reverse order?' If yes, use a stack.
Bracket matching: Push openers, pop on closers, verify match. Valid if stack is empty at the end.
Monotonic stack: Maintain sorted order on the stack. Pop when a new element violates the order — the pop resolves the 'next greater/smaller' query.
Decreasing stack: Pop smaller elements when a larger one arrives → finds 'next greater element'. Used in Daily Temperatures, Stock Span.
Increasing stack: Pop larger elements when a smaller one arrives → finds 'next smaller element'. Used in Largest Rectangle, Trapping Rain Water.
Expression eval: Push numbers, pop two on operator, compute, push result. Watch operand order for - and /.
State saving: '[' or '(' saves current state (push). ']' or ')' restores and combines (pop). Used in Decode String, Calculator.
Undo pattern: Push valid items, pop to undo. Used in Simplify Path, Backspace Compare, Remove Duplicates.
Sentinel trick: Append a dummy element (e.g., height 0) to flush remaining stack items. Avoids post-loop cleanup code.
Push indices: Almost always push indices, not values. Indices give you both position (for distance) and value (via lookup).
Amortized O(n): Each element is pushed once and popped at most once → total operations = O(2n) = O(n), even though there's a while loop inside the for loop.
Mental trigger: "Next greater" or "matching brackets" or "nested structure" or "undo the last thing" → Stack.
Decision Flowchart
Does the problem involve matching or validating nested tokens? ├── YES → Bracket Matching template (push openers, pop on closers) └── NO → Does the problem ask for "next greater" or "next smaller" element? ├── YES → Monotonic Stack (decreasing for next greater, increasing for next smaller) └── NO → Does the problem involve evaluating an expression or calculator? ├── YES → Expression Evaluation template (number stack + operator handling) └── NO → Does the problem involve undoing, backtracking, or simplifying? ├── YES → Undo/State template (push valid items, pop to undo) └── NO → Does the problem involve computing areas or spans based on boundaries? ├── YES → Monotonic Stack (histogram pattern) └── NO → Does the problem have nested/recursive structure? ├── YES → State-saving stack (push on open, pop on close) └── NO → Stack probably isn't the right pattern → Consider Queue, Heap, or Two Pointers
Stack Operations Quick Reference
| Operation | Time | Description | Common Pitfall |
|---|---|---|---|
| push(x) | O(1) | Add element to top | Pushing values instead of indices |
| pop() | O(1) | Remove and return top element | Popping from empty stack |
| peek() / top() | O(1) | View top element without removing | Confusing peek with pop |
| isEmpty() | O(1) | Check if stack has elements | Forgetting to check before pop |
| size() | O(1) | Number of elements in stack | Off-by-one in size checks |
One last thing
The stack is deceptively simple — just push and pop. But it's the backbone of some of the hardest interview problems (Largest Rectangle, Trapping Rain Water, Basic Calculator). The key isn't memorizing solutions — it's recognizing the LIFO signal in the problem and knowing what to push, when to pop, and what invariant the stack maintains. Master those three decisions, and every stack problem becomes a variation of the same theme.