Data Structures · Stacks · A Field Guide

Design a min stack

Push and pop in O(1) is free. The trick is answering "what's the minimum right now?" instantly — without ever scanning.

push x pop findMin → O(1)
01 · The requirement

A stack that also knows its minimum — instantly

Build a stack with three operations, all in O(1): push a value, pop the top, and return the current minimum. Push and pop are trivial on any stack. The hard constraint is findMin() in constant time.

The principle it forces If a query must answer in O(1), the information it needs has to be already maintained — you can't go compute it on demand. So the minimum must be kept up to date as the stack changes.

02 · Why the obvious ideas fail

Scanning is O(N); one variable forgets

Scanning the stack for its smallest value is O(N) — banned by the requirement. So keep a running min variable? Push handles it fine with min(oldMin, x). But pop breaks it:

Push is easy
min = min(min, x)
Each new value can only lower or keep the minimum.
Pop is the trap
min = ???
Remove the current minimum and the previous one is gone forever.

Push 5, 3 → min is 3. Pop the 3 and the stack is [5], so the min should be 5 — but a single variable never recorded it. The real need is minimum history: what was the minimum before each element arrived.


03 · The idea in pictures

Keep a running record, not a single number

Save-game snapshots

Every push is a save point that records the stack and its minimum. Popping loads the previous save — the min it had back then comes back automatically.

A shadow stack

A second stack walks in lockstep with the first. Each slot remembers the smallest value seen up to that depth, so its top is always today's minimum.

Undo history

Because pops reverse pushes in LIFO order, the minimums are needed back in reverse too — which is exactly what a stack gives you.


04 · Try it live

Two stacks, moving in lockstep

Push values and pop them. The main stack holds your values; the min stack pushes min(x, currentMin) alongside every value. Watch how the top of the min stack is always the answer to findMin() — and how popping restores the previous minimum for free.

Min stack visualizer

findMin() → −1
main stack
min stack
Empty. Push a value to begin.
both stacks always stay the same height — push to both, pop from both

05 · Why it works

The invariant that makes it O(1)

The invariant At every depth, the min stack stores the minimum of the matching prefix of the main stack. So its top is always the minimum of the whole current stack.

Push 2, 3, 1, 4 and the minimum after each push is 2, 2, 1, 1. The two stacks line up slot for slot:

Depth Main stack Min stack findMin
1 2 2 2
2 2 3 2 2 2
3 2 3 1 2 2 1 1
4 2 3 1 4 2 2 1 1 1

Because each value carries its own paired minimum, popping never requires a recompute — remove the top of both stacks and the previous minimum is sitting right there.


06 · The code

Two parallel stacks

import java.util.*;

class MinStack {
    private final Deque<Integer> stack = new ArrayDeque<>();
    private final Deque<Integer> minStack = new ArrayDeque<>();

    void push(int x) {
        stack.push(x);
        if (minStack.isEmpty()) minStack.push(x);
        else minStack.push(Math.min(x, minStack.peek()));
    }

    void pop() {
        if (stack.isEmpty()) return;
        stack.pop();
        minStack.pop();            // stay in lockstep
    }

    int findMin() {
        return stack.isEmpty() ? -1 : minStack.peek();
    }
}

Every operation is a constant number of stack calls, so push, pop, and findMin are all O(1). Both stacks hold up to N items, giving O(N) space — the classic trade of memory for query speed. Prefer ArrayDeque over the legacy Stack class.


07 · The duplicate-minimum trap

If you compress the min stack, use <=

A space-saving variant pushes to the min stack only when a value is a new minimum, and pops it only when the popped value equals the current min. It works — but the comparison must be <=, not <.

Why strict < breaks Push 2, then 2 again. With <, the second 2 never reaches the min stack, so min = [2]. Pop one 2, the rule removes that lone 2, and now the min stack is empty while a 2 still sits in main. Using <= records both copies, so each pop retires exactly one.
// sparse variant — note the <=
void push(int x) {
    stack.push(x);
    if (minStack.isEmpty() || x <= minStack.peek()) minStack.push(x);
}
void pop() {
    int removed = stack.pop();
    if (removed == minStack.peek()) minStack.pop();
}

It's still O(N) in the worst case — a strictly decreasing input makes every value a new minimum. For learning, the push-to-both version is safer; the compression just trades a little clarity for occasional space.


08 · Same idea, one stack

Store the min inside each entry

Two parallel stacks and one stack of (value, minSoFar) pairs are the same idea wearing different clothes. Each entry carries the minimum as of when it was pushed, so the top always knows the current minimum.

record Entry(int value, int minSoFar) {}

Deque<Entry> stack = new ArrayDeque<>();

void push(int x) {
    int m = stack.isEmpty() ? x : Math.min(x, stack.peek().minSoFar());
    stack.push(new Entry(x, m));
}
int findMin() { return stack.isEmpty() ? -1 : stack.peek().minSoFar(); }
One more level: O(1) extra space An advanced trick encodes values as 2*x − currentMin to smuggle the previous minimum into the stored number, using just one min variable and no second stack. It works but overflows easily (use long) and is easy to get wrong — reach for it only after the two-stack version is second nature.

09 · The bigger pattern

Augment the structure with state at every depth

This isn't really about minimums. It's a template: whenever a data structure needs an extra aggregate query in O(1), store that aggregate at every depth so it updates incrementally. The recurrence is always state[i] = combine(state[i−1], newValue).

Stack + min
min(prev, x)
this problem
Stack + max
max(prev, x)
same shape, flipped
Stack + gcd
gcd(prev, x)
running gcd of contents
Stack + sum
prev + x
running aggregate

The same instinct powers prefix sums, segment trees, and augmented trees: store extra information when maintaining it beats recomputing it later.


10 · One month from now

Reconstruct it from one question

  1. A normal stack gives push and pop in O(1), but min needs a scan — O(N).
  2. To answer instantly, maintain the minimum as the stack changes.
  3. One variable fails: popping the min loses the previous min.
  4. So I need minimum history — and history in LIFO order is a stack.
  5. Push value and min-so-far together; pop both; findMin is the min stack's top.
The question that unlocks it Don't ask "how do I find the minimum fast?" Ask "what will I wish I'd kept after a pop?" The answer — the previous minimum — makes the second stack obvious. When an operation destroys information you'll need later, preserve it incrementally before it's gone.