latency.lab
Algorithms · Topic 04

Stack

Level: foundationTool: LIFO order, and the monotonic-stack trickTime: ~55 min

By the end you can

A stack is the simplest possible container — push, pop, peek at the top, nothing else — and yet it's the exact right tool the moment a problem has a "most recent unresolved thing" shape: the most recently opened bracket, the most recent nested group, the most recent right-moving asteroid still in flight. Recognizing that shape is the actual skill; the container itself is trivial.

Why LIFO order is the whole point

A stack is Last-In-First-Out for a reason: in every problem below, whatever you need to resolve next is whatever was most recently left unresolved. Bracket matching is the clearest case — "([)]" is invalid precisely because the ) needs to match the most recent unmatched (, not just any earlier one. A queue (FIFO) would get this wrong; a stack gets it right for free, just by existing.

The habit to build

Ask: "does resolving the current item depend on the most recently seen unresolved item?" Bracket matching, nested decode groups, and asteroid collisions all say yes — that's your stack signal. If order genuinely doesn't matter (just "have I seen this before"), that's a hash set instead, not a stack.

(
[
stack after "([" — top is [
next char:
)
(
[
")" needs "(" but top is "[" — invalid
"([)]" is invalid: after pushing ( then [, the next character ) must match whatever's on TOP of the stack right now — [, not the ( underneath it. A queue would incorrectly check the OLDEST unmatched bracket instead.

The monotonic stack: the pattern hiding inside "next greater/smaller"

Daily Temperatures, Next Greater Element, and Largest Rectangle in Histogram all look like they need an O(n²) "scan forward/backward from every position" approach — and a monotonic stack is what turns that into O(n). The idea: keep a stack whose values are always in sorted order (increasing or decreasing, depending on the problem) from bottom to top. When a new element would break that order, pop everything that it invalidates — and the reason this is fast is that each element only ever gets pushed once and popped once across the entire run, no matter how many times the while loop looks like it's doing extra work.

why-each-index-is-touched-twice.cpp
// This LOOKS like it could be O(n^2) -- a while loop nested inside a for
// loop. It isn't, because of one fact: every index gets pushed exactly
// once and popped at most once across the WHOLE run, not once per
// outer iteration.
for (int i = 0; i < n; ++i) {
    while (!st.empty() && temperatures[st.top()] < temperatures[i]) {
        // this index is popped HERE and never pushed again -- it's
        // permanently resolved, not re-examined on a future i
        result[st.top()] = i - st.top();
        st.pop();
    }
    st.push(i);   // pushed exactly once, ever
}
// Total pushes: n. Total pops: at most n. Total work: O(n), not O(n^2).

Stack-as-scratchpad: building output incrementally

A second recurring shape: some problems use a stack not to track "what's still pending" but as a scratchpad for building a result where later input can retroactively undo earlier output — Backspace String Compare (a # undoes the last character), Remove All Adjacent Duplicates (a matching character cancels the one before it), and Decode String (a closing bracket finishes and folds in everything built since the matching open). In all three, a plain string-append-only approach can't handle the "undo," but a stack's pop handles it directly.

Common mistakes

  • Popping without checking empty() first — an unclosed bracket, an extra backspace, or a malformed input will call .top()/.pop() on an empty stack, which is undefined behavior, not a clean crash you can rely on.
  • Getting the monotonic stack's direction backwards — "next greater" needs a decreasing stack (pop when the new value is bigger); "next smaller" needs the opposite. Copy-pasting the wrong comparison silently solves a different problem.
  • Off-by-one on width calculations after a pop (Largest Rectangle in Histogram) — the popped bar's width depends on what's now on top of the stack (its left boundary) and the current index (its right boundary), and getting the boundary exclusive/inclusive wrong is the single most common bug in this problem.
  • Re-scanning from the start after a stack-driven "cascade" (Remove All Adjacent Duplicates) instead of trusting the stack — the whole point of using a stack here is that popping already exposes the next comparison for free; a fresh re-scan turns an O(n) solution back into O(n²).
  • Forgetting the circular wraparound needs a bounded second pass (Next Greater Element II) — iterating 2n times but still pushing new indices on the second lap causes indices to be considered twice, corrupting the monotonic order.

Takeaways

  • Reach for a stack when resolving the current item depends on the most recently seen unresolved item — bracket matching, nested groups, "what's still in flight."
  • A monotonic stack turns an O(n²) "next greater/smaller" scan into O(n), because each index is pushed once and popped at most once across the entire run — not once per outer iteration, despite the nested-loop look.
  • A stack also works as a scratchpad for building output where later input can undo earlier output (backspaces, cancelling duplicates, closing a nested group) — something a plain append-only string can't do cleanly.
  • Always check empty() before popping — malformed or edge-case input is exactly when a stack-based solution is tempted to skip this check.

Try it: the basic LIFO shape

Valid Parentheses is the purest demonstration that a stack's order — not just its existence — is what makes the algorithm correct.

Live

Loading starter code…

Try it: the monotonic stack

Daily Temperatures is the canonical monotonic-stack problem — the walkthrough above is this exact algorithm, in full.

Live

Loading starter code…

Try it: stack as a scratchpad

Decode String is the clearest case of using a stack to build output where later input (a closing bracket) folds in everything built since the matching open.

Live

Loading starter code…

Keep going

That's 3 of the 12 problems in this topic. Largest Rectangle in Histogram is worth doing once these three feel automatic — it's the same monotonic-stack idea with a trickier width calculation on top. See the full Stack set →

Checkpoint · Stack

5 questions · pass at 70%

Q01
Why is a stack (LIFO) the right container for Valid Parentheses, rather than a queue (FIFO)?
Why"([)]" is invalid because the ")" needs to match the most recent unmatched "(", not an earlier one. That's precisely LIFO order — a stack gets this right by construction; a queue would check the wrong (oldest) open bracket.
Q02
Daily Temperatures uses a while loop nested inside a for loop, which looks like O(n²). Why is it actually O(n)?
WhyThe nested-loop shape is misleading: an index that gets popped is permanently resolved and never pushed again. Since every index is pushed once and popped at most once total (not once per outer step), the whole algorithm does O(n) total work.
Q03
What's the actual bug in re-scanning a string from the beginning after removing an adjacent-duplicate pair, instead of trusting the stack to have already exposed the next comparison?
WhyThe whole benefit of the stack-based approach is that popping a matched pair automatically makes the correct next comparison available at the top of the stack — no separate rescan needed. Re-scanning from the start throws that benefit away for no correctness gain, just wasted time.
Q04
Next Greater Element II handles a circular array by iterating 2n times using i % n as the index. Why must new indices only be pushed during the FIRST pass (i < n), not the second?
WhyThe second lap exists purely to let earlier, still-unresolved indices find a match that wraps around to the beginning. If new indices were pushed again during that lap, the same index could be pushed twice, breaking the invariant that each index is resolved exactly once.
Q05
In Largest Rectangle in Histogram, when a bar is popped off the monotonic stack, what determines the width of its rectangle?
WhyPopping a bar means it can't extend any further right (the current bar is shorter). Its rectangle's width spans from just after whatever is now exposed at the top of the stack to the current index — exactly the boundary calculation that's easy to get off-by-one on.

Finished Stack?

Pass the quiz to complete it automatically.