Stack

A stack's LIFO discipline does two completely different jobs: it pairs and defers work (brackets, expressions, undo) and it drives the monotonic stack that resolves next-greater / next-smaller queries in a single O(n) pass.

Topic guide9 problems
The unlock

A stack means “most-recent-unresolved-thing first”: the top is always the newest thing you haven't finished dealing with — so whenever the next inputresolves something, the thing it resolves is sitting right on top.

MENTAL MODEL A pile of deferred work, innermost on top

Picture a desk where you stack tasks you can't finish yet. Each new unfinished thing goes on top, and you're only ever allowed to touch the top. The instant something arrives that completesthe most recent unfinished thing, it's right there waiting — you pop it, done.

That single rule powers two patterns that look unrelated but are the same idea:

  • Matching & nesting. Push an open thing; pop it when its closer arrives. The top is always the innermost still-open item — exactly the one a closing bracket must answer to.
  • Monotonic stack.Keep the stack sorted. When a new element arrives, pop everything it “beats” — and each thing you pop just found its answer.
The reframe → don't ask “what container do I use?” Ask “when the next input shows up, does it resolve the most recent unfinished thing?” If yes, that unfinished thing belongs on a stack.

SEE IT Brackets close from the inside out

Watch "([])" match. Every open bracket is pushed; every close pops and checks the top. Nesting falls out for free because the top is always the innermost open bracket:

s = "( [ ] )"

read '('  push          stack: [ ( ]        ← innermost open on top
read '['  push          stack: [ ( [ ]      ← '[' is now the open thing
read ']'  pop matches?  pop '[' vs ']'  ✓   stack: [ ( ]
read ')'  pop matches?  pop '(' vs ')'  ✓   stack: [ ]

end: stack empty  →  every open bracket got closed  →  VALID

The TOP is always the most-recent unresolved open bracket — the
exact one a closing bracket must answer to. That is why a stack,
and only a stack, matches nesting.
The smell test → if a problem is about things that open and close, or that nest inside each other, the most-recent-open is what you need next — and a stack hands it to you in O(1).

SEE IT A taller element pops everyone it towers over

Now the monotonic version. We keep a decreasing stack on [2, 1, 3] to find each element's next-greater. When 3 arrives it cascades — popping the 1 then the 2, and each pop resolves one waiting element:

arr = [ 2, 1, 3 ]      stack holds indices waiting for a bigger value
                       (values strictly DECREASING bottom→top)

i=0  val 2   stack empty, push          stack(vals): [ 2 ]
i=1  val 1   1 < 2, push                stack(vals): [ 2, 1 ]
i=2  val 3   3 > 1 → pop 1, ans[1] = 3  stack(vals): [ 2 ]
             3 > 2 → pop 2, ans[0] = 3  stack(vals): [ ]
             push 3                     stack(vals): [ 3 ]

leftover: 3 has nobody bigger to its right → ans[2] = -1
result:  [ 3, 3, -1 ]

The arriving 3 "beats" everything it towers over and pops them.
Each popped element just found its next-greater answer — and each
index is pushed once, popped once → O(n) despite the inner loop.
Why it's O(n), not O(n²) → the inner while looks nested, but each index is pushed once and popped once over the whole run. Total pops ≤ total pushes = n. The nesting is an illusion.

HOW TO THINK The cold-start ladder — run this on any new problem

When you suspect a stack, climb these rungs in order:

  1. Do I keep needing the most recent unresolved item?Brackets, undo, postfix operands, “the last open thing” — if yes, reach for a stack.
  2. Is it matching / nesting? Push when something opens; pop and verify when its closer arrives. A leftover non-empty stack means something never closed.
  3. Am I asking “next/previous greater/smaller”?That's a monotonic stack. Decide the pop direction by the answer the popped element gets: pop when the newcomer is bigger → popped elements get a next-greater (decreasing stack); pop when smaller → next-smaller (increasing stack).
  4. Push the index, not the value. You almost always need i − j distance or a span width when you pop — get the value back with arr[index].
  5. Handle the leftovers. Whatever is still on the stack after the loop never got resolved: its answer is the default (-1), or for matching problems it means the input is invalid.
The one question that picks the direction →“when I pop someone, what did they just learn?” If they learned their next-greater, the stack is decreasing and you pop on a bigger newcomer. Say the answer out loud and the comparison sign follows.

SAY IT Name what the stack is waiting for

Before coding, say in one sentence what each item on the stack is waiting for. If you can finish that sentence, the push/pop logic is decided.

  • Valid Parentheses:“each item is an open bracket waiting for its matching close.”
  • Daily Temperatures:“each index is a day waiting for a warmer day; a warmer day pops it and records the distance.”
  • Next Greater Element:“each index waits for a bigger value to its right; a bigger value pops it and is its answer.”
  • Largest Rectangle:“each bar waits for a shorter bar; a shorter bar pops it and fixes its right edge.”
The invariant → a monotonic stack is always sorted (decreasing for next-greater, increasing for next-smaller). The pop loop exists precisely to restorethat order before each push. If your stack isn't sorted after a push, your comparison sign is wrong.

LOOP SHAPE Every monotonic stack is this skeleton in disguise

Strip away the specific problem and every monotonic-stack solution is the same three moves: pop everyone the newcomer beats (they just found their answer) → record it → push the newcomer to wait for its own. Read it as a sentence:

for each index i, left to right:

    while stack not empty AND arr[i] beats arr[stack.top]:
        j = stack.pop()        # j was WAITING — i is its answer
        record answer for j using i      # value arr[i] or distance i - j

    push i                     # i now waits for ITS answer

# anything still on the stack never found an answer → default (-1)

The onlythings that change between problems are the “beats” comparison (> vs <, strict vs non-strict for duplicates) and what you record on a pop (a value, a distance, or an area). The shape never changes.

MNEMONIC Pop everything you tower over.

Pop everything you tower over. A monotonic stack holds items still waiting for an answer; when a new item beats them, it resolves and pops them all. Each index is pushed and popped once → O(n). Step the Visualize tab to watch the next-greater-element cascade.

PATTERN LIFO bookkeeping — deferring and pairing

A stack stores work that is incomplete until some future condition is met. You push something now and pop it later when you encounter its match or resolution. Classic examples:

  • Bracket matching. Push each opening bracket; when a closing bracket arrives, pop and verify the pair.
  • Expression evaluation (RPN / postfix). Push operands; on an operator, pop two operands, compute, push the result.
  • Undo / most-recent-first.Any "reverse chronological" access pattern maps directly onto a stack.

In all these cases the stack is essentially a scratchpad of open obligations waiting to be closed.

KEY IDEA The monotonic stack — O(n) next-greater / next-smaller

A monotonic stack maintains elements in sorted order by aggressively popping elements that violate the invariant as each new element arrives. Every element is pushed exactly once and popped at most once, so the total work is O(n) even though there is a nested while loop inside a for.

Invariant → the stack (bottom to top) is monotonically decreasing for next-greater problems, and monotonically increasing for next-smaller problems. The moment you push a value that breaks the invariant, every popped element has found its answer.

Crucially, you almost always push the index, not the value — that way, when you pop, you can compute distances or widths.

COST Amortized O(n) — why the nested loop is not O(n²)

Brute-force scan
O(n²)
For every element, scan right until a greater one is found.
Monotonic stack
O(n)
Each element pushed once, popped at most once.

The inner while loop can pop at most n elements across the entire outer loop — the total number of pops is bounded by the total number of pushes, which is exactly n. This is the standard amortized argument.

SHAPES Decreasing vs increasing stack — how to choose

  • Monotonic decreasing (values ↓ from bottom to top). Pop when the incoming value is greater. Answers "next greater element" or "next warmer day" — popped element's answer is the current index/value.
  • Monotonic increasing (values ↑ from bottom to top). Pop when the incoming value is smaller. Answers "next smaller element", "previous smaller", and histogram-style span queries.

For histogram / span problems (Largest Rectangle, Car Fleet) you often need both boundaries — loop left-to-right with an increasing stack and use the index gap to compute width.

RUN IT Pop everything you tower over

step 0 / 16
STARTFor each bar, find the next taller bar to its right. Keep a stack of bars still waiting for their answer. Pop everything you tower over.
2
0
1
1
2
2
4
3
3
4
1
5
stack (indices, values ↓)
empty
next-greater answers
·
·
·
·
·
·
current baron the stack (waiting)just resolved
slowfast

TRIGGERS When you see ___ → reach for ___

Reach for a stack when you need to remember things in reverse order— either to match/cancel them (brackets, pairs) or to resolve a "what was the last X before this?" query (monotonic). If the problem involves the nearest element that is greater or smaller, the monotonic stack is almost always the right tool.

matching brackets / valid parenthesespush opens, pop and verify on closes
evaluate expression / postfix / RPNpush operands, pop two on each operator
next greater / next warmer elementmonotonic decreasing stack of indices
previous smaller / nearest smaller to the leftmonotonic increasing stack; answer is the new top after popping
largest rectangle / maximum span / histogram areamonotonic increasing stack of indices; width = i − stack top − 1
undo / most recently added / reverse-chronological accessplain LIFO stack
car fleet / group by arrival time, latest dominatessort descending, monotonic stack of times

RED FLAGSWhen it's NOT this pattern

  • You need FIFO order (process oldest first).That's a queue, not a stack. A common mistake is using a stack when BFS level-order traversal is required.
  • You need random access by index. Stacks only expose the top. If you need to read or update an arbitrary position, you want a plain array or a different data structure.
  • You need the global minimum or maximum efficiently.A heap (priority queue) serves that role. A plain stack can only tell you what's on top, not the extreme of all current elements — unless you augment it (see Min Stack, #155).
  • The "next greater" is in a 2-D grid or graph. The monotonic stack is linear; 2-D extensions usually require BFS/DFS or a different sweep technique.

TEMPLATE Basic match / pair stack

When → Any problem that involves matching opening and closing delimiters or pairing elements that cancel each other out (brackets, tags, asterisks). Push openings, pop and verify on each closing.

basic-match-pair-stack.ts
function isValid(s: string): boolean {
  const stack: string[] = [];
  const close: Record<string, string> = { ')': '(', ']': '[', '}': '{' };

  for (const ch of s) {
    if (!close[ch]) {
      stack.push(ch);               // opening bracket → defer it
    } else {
      if (stack.pop() !== close[ch]) return false;  // must match top
    }
  }
  return stack.length === 0;        // nothing left unmatched
}
Empty-stack check on pop → always guard with stack.length before popping, or the pop returns undefined (JavaScript) and the comparison silently fails instead of throwing.

TEMPLATE Monotonic decreasing — next greater

When → The problem asks for the next element to the right that is greater (or the distance to it). Keep a decreasing stack of indices; pop when the incoming value exceeds the top.

monotonic-decreasing-next-greater.ts
// Monotonic DECREASING stack → find the NEXT GREATER element to the right.
// Stack holds indices of elements waiting for their "answer".
function nextGreater(arr: number[]): number[] {
  const n = arr.length;
  const res = new Array<number>(n).fill(-1);
  const stack: number[] = [];         // indices, values are decreasing top→bottom

  for (let i = 0; i < n; i++) {
    // Pop while the current element is GREATER than the top of stack.
    // Those popped elements have found their next-greater answer.
    while (stack.length && arr[i] > arr[stack[stack.length - 1]]) {
      res[stack.pop()!] = arr[i];
    }
    stack.push(i);                    // push index, NOT value
  }
  return res;                         // remaining indices keep -1 (no greater)
}
Push the index, not the value → you need the index to compute the distance (e.g. Daily Temperatures returns i − stack.pop()). Retrieve the value with arr[index] when comparing.

TEMPLATE Monotonic increasing — next smaller

When → The problem asks for the next element to the right that is smaller, or for a left-boundary (previous smaller) by iterating right-to-left. Keep an increasing stack of indices; pop when the incoming value is less than the top.

monotonic-increasing-next-smaller.ts
// Monotonic INCREASING stack → find the NEXT SMALLER element to the right.
// Stack holds indices of elements waiting for their "answer".
function nextSmaller(arr: number[]): number[] {
  const n = arr.length;
  const res = new Array<number>(n).fill(-1);
  const stack: number[] = [];         // indices, values are increasing top→bottom

  for (let i = 0; i < n; i++) {
    // Pop while the current element is SMALLER than the top of stack.
    while (stack.length && arr[i] < arr[stack[stack.length - 1]]) {
      res[stack.pop()!] = arr[i];
    }
    stack.push(i);
  }
  return res;
}

TEMPLATE Histogram / span — increasing stack of indices

When → Problems that compute a max area or span bounded by a shorter bar on either side (Largest Rectangle in Histogram, Trapping Rain Water variant). Append a sentinel 0 to flush the stack cleanly at the end.

histogram-span-increasing-stack-of-indices.ts
// Largest rectangle in histogram — monotonic increasing stack of bar indices.
// When a shorter bar arrives, pop taller bars and compute their max-width rectangle.
function largestRectangle(heights: number[]): number {
  const bars = [...heights, 0];       // sentinel 0 flushes all remaining bars
  const stack: number[] = [];         // increasing stack of indices
  let maxArea = 0;

  for (let i = 0; i < bars.length; i++) {
    while (stack.length && bars[i] < bars[stack[stack.length - 1]]) {
      const h = bars[stack.pop()!];
      // Width: from the new top-of-stack (exclusive) to i (exclusive)
      const w = stack.length ? i - stack[stack.length - 1] - 1 : i;
      maxArea = Math.max(maxArea, h * w);
    }
    stack.push(i);
  }
  return maxArea;
}
Width calculation → when you pop index j, the left boundary is the new stack top (exclusive) and the right boundary is i (exclusive). If the stack is empty after the pop, the bar at j is the shortest seen so far and its width spans all the way to the left edge: w = i.

PITFALL Popping an empty stack

In JavaScript/TypeScript, Array.prototype.pop() on an empty array returns undefined instead of throwing. This silently corrupts comparisons. Always guard with stack.length > 0 before popping, or assert with stack.pop()! only after the guard.

PITFALL Pushing value instead of index

Monotonic stack problems almost always require the index on the stack, not the value. When you pop to record an answer, you need the index to compute the distance (i − j) or the span width. Storing the value makes that calculation impossible. Retrieve the value via arr[stack.top] during comparison.

PITFALL Strict vs non-strict comparison — handling duplicates

Whether you use > or >= (and < vs <=) in the "pop while" condition decides what happens with equal elements. Using >=pops duplicates eagerly (only the rightmost duplicate records the "next greater"); using >keeps duplicates on the stack so each one can answer independently. Read the problem statement carefully — "strictly greater" vs "greater or equal" changes the invariant.

PITFALL Forgetting to process the leftover stack after the loop

After the main loop finishes, elements still on a monotonic stack have no next greater/smaller element — their answer is -1 (or 0, depending on the problem). For match/pair problems, a non-empty stack after the loop means unmatched opening brackets — the string is invalid. A common bug is returning early without this check.

PROBLEMS

#155Min StackMaintain an auxiliary "min stack" in parallel with the main stack; each push records the current minimum so pop can restore it in O(1).#150Evaluate Reverse Polish NotationPush operands; on each operator pop two, compute the result, push it back. Watch the operand order for non-commutative ops (subtraction, division).#22Generate ParenthesesBacktracking with open/close counters acts like a stack: recurse by adding '(' while open < n, and ')' while close < open.#739Daily TemperaturesMonotonic decreasing stack of indices. When a warmer day i arrives, pop j while temps[i] > temps[j]; answer[j] = i − j.#853Car FleetSort by start position descending. Compute each car's arrival time; push onto a stack and pop if the top arrives no later (it merges into the fleet behind). Stack size is the answer.#84Largest Rectangle in HistogramMonotonic increasing stack of bar indices. Append a 0-height sentinel to flush remaining bars. On each pop, width = i − new-top − 1 (or i if stack is empty).#224Basic CalculatorEvaluate a string with +, -, parentheses and non-negative integers in one linear scan, using a stack to save the partial result and sign before each open parenthesis and restore them on the matching close.#32Longest Valid ParenthesesFind the length of the longest valid parentheses substring. A stack of indices seeded with -1 as a base measures each valid span as i minus the stack top in one O(n) pass.#85Maximal RectangleFind the largest rectangle of 1s in a binary matrix by treating each row as a histogram of consecutive 1s above it, then running Largest Rectangle in Histogram (a monotonic stack) once per row, for O(rows × cols).