StackLIFOMonotonic StackExpression ParsingNested Structures

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.

45 min read10 sections
01

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.

02

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

PatternWhen to UseKey Difference
Stack (LIFO)Matching in reverse order, nested structures, undo operationsLast-in, first-out — access only the most recent item
Queue (FIFO)BFS, level-order traversal, task schedulingFirst-in, first-out — process in arrival order
Monotonic StackNext greater/smaller element, histogram areas, stock spanStack variant that maintains sorted order — elements are popped when they violate the monotonic property
Monotonic DequeSliding window min/maxDouble-ended queue maintaining monotonic order within a window
Heap / Priority QueueTop-K elements, streaming min/max, merge K sorted listsGives min/max in O(log n) — not LIFO, but priority-based
Recursion (Call Stack)Tree/graph DFS, divide and conquerImplicit 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.

03

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.

1

Brute Force — Check Every Future Day

O(n²) time · O(1) space (excluding output)

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.

Brute Forcetypescript
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²).
2

Optimal — Monotonic Stack (Decreasing)

O(n) time · O(n) space

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.

Monotonic Stack — Optimaltypescript
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?

1

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.

2

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.

3

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.

04

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.

Bracket Matching Templatetypescript
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.

Monotonic Stack — Next Greater Elementtypescript
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.

Basic Calculator Templatetypescript
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.

Stack for Undo Operationstypescript
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.

05

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.

VariationStack StrategyClassic Example
Bracket MatchingPush openers, pop on closers, verify matchValid Parentheses, Remove Invalid Parentheses
Monotonic Stack (Decreasing)Pop smaller elements when a larger one arrivesDaily Temperatures, Next Greater Element
Monotonic Stack (Increasing)Pop larger elements when a smaller one arrivesLargest Rectangle in Histogram, Trapping Rain Water
Expression EvaluationNumber stack + operator handling with precedenceBasic Calculator, Evaluate RPN
String Decoding / BuildingPush partial results, pop and combine on closing delimiterDecode String, Remove Duplicate Letters
Undo / SimplificationPush valid items, pop to undo or backtrackSimplify Path, Backspace String Compare
Min/Max StackAuxiliary stack tracking running min/max alongside valuesMin Stack, Max Stack
Simulation (Collision / Merge)Push items, pop when they interact with the new itemAsteroid 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.

Largest Rectangle in Histogramtypescript
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.

Decode Stringtypescript
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.

Asteroid Collisiontypescript
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.
06

Visual Walkthrough

Let's trace through three classic stack problems step by step to see the pattern in action.

Valid Parentheses — Step by Step

Valid Parentheses — Tracetext
Input: "({[]})"

Step 1: ch = '('opening bracketpush
  stack = ['(']

Step 2: ch = '{'opening bracketpush
  stack = ['(', '{']

Step 3: ch = '['opening bracketpush
  stack = ['(', '{', '[']

Step 4: ch = ']'closing bracketpop '[', check: pairs[']'] = '['
  stack = ['(', '{']

Step 5: ch = '}'closing bracketpop '{', check: pairs['}'] = '{'
  stack = ['(']

Step 6: ch = ')'closing bracketpop '(', check: pairs[')'] = '('
  stack = []

Stack is emptyVALID

Key: Each closer matches the MOST RECENT openerthat's LIFO.
The stack naturally handles arbitrary nesting depth.

Daily Temperatures (Monotonic Stack) — Step by Step

Daily Temperatures — Tracetext
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 emptypush 0
  stack = [0]

i=1: temp=74 > temps[0]=73pop 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]=74pop 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]=75no pop, push 3
  stack = [2,3]

i=4: temp=69 < temps[3]=71no pop, push 4
  stack = [2,3,4]

i=5: temp=72 > temps[4]=69pop 4, result[4] = 5-4 = 1
  temp=72 > temps[3]=71pop 3, result[3] = 5-3 = 2
  temp=72 < temps[2]=75stop, push 5
  stack = [2,5]  result = [1,1,0,2,1,0,0,0]

i=6: temp=76 > temps[5]=72pop 5, result[5] = 6-5 = 1
  temp=76 > temps[2]=75pop 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]=76no pop, push 7
  stack = [6,7]  result = [1,1,4,2,1,1,0,0]

Done. Indices 6,7 remain on stackno warmer dayresult 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

Largest Rectangle — Tracetext
Input: heights = [2, 1, 5, 6, 2, 3]
stack = [], maxArea = 0

i=0: h=2, stack emptypush 0
  stack = [0]

i=1: h=1 < heights[0]=2pop 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]=1push 2
  stack = [1,2]

i=3: h=6 > heights[2]=5push 3
  stack = [1,2,3]

i=4: h=2 < heights[3]=6pop 3
  height=6, width = 4-2-1 = 1, area = 6, maxArea = 6
  h=2 < heights[2]=5pop 2
  height=5, width = 4-1-1 = 2, area = 10, maxArea = 10
  h=2heights[1]=1stop, push 4
  stack = [1,4]

i=5: h=3 > heights[4]=2push 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.
07

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.

1

LC 20. Valid Parentheses

Easy

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.

2

LC 155. Min Stack

Medium

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.

3

LC 150. Evaluate Reverse Polish Notation

Medium

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.

4

LC 739. Daily Temperatures

Medium

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

5

LC 394. Decode String

Medium

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.

6

LC 84. Largest Rectangle in Histogram

Hard

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.

7

LC 42. Trapping Rain Water

Hard

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.

08

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

09

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

1

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.

2

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.

3

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.

4

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.

5

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-UpHow 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.

10

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

When to Use a Stack — Decision Treetext
Does the problem involve matching or validating nested tokens?
├── YESBracket Matching template (push openers, pop on closers)
└── NODoes the problem ask for "next greater" or "next smaller" element?
          ├── YESMonotonic Stack (decreasing for next greater, increasing for next smaller)
          └── NODoes the problem involve evaluating an expression or calculator?
                    ├── YESExpression Evaluation template (number stack + operator handling)
                    └── NODoes the problem involve undoing, backtracking, or simplifying?
                              ├── YESUndo/State template (push valid items, pop to undo)
                              └── NODoes the problem involve computing areas or spans based on boundaries?
                                        ├── YESMonotonic Stack (histogram pattern)
                                        └── NODoes the problem have nested/recursive structure?
                                                  ├── YESState-saving stack (push on open, pop on close)
                                                  └── NOStack probably isn't the right pattern
Consider Queue, Heap, or Two Pointers

Stack Operations Quick Reference

OperationTimeDescriptionCommon Pitfall
push(x)O(1)Add element to topPushing values instead of indices
pop()O(1)Remove and return top elementPopping from empty stack
peek() / top()O(1)View top element without removingConfusing peek with pop
isEmpty()O(1)Check if stack has elementsForgetting to check before pop
size()O(1)Number of elements in stackOff-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.