Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Keep an auxiliary 'min' stack alongside the main stack. Each time you push, also push the running minimum (min of the new value and the current min) onto the min stack; pop both together. getMin() is just the top of the min stack. Every operation — push, pop, top, getMin — is O(1). The trick is storing the minimum *as of each level*, so popping naturally restores the previous minimum.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can design a data structure that maintains an O(1) auxiliary query under pushes and pops — specifically, how popping rolls back the minimum without recomputation.
Where candidates slip
Storing a single 'min' variable that can't be restored after the minimum is popped, or scanning the stack for getMin() (O(n)).
How to stand out
Explain why a single min variable fails (it can't recover the previous min on pop), present the parallel min stack, and mention the space-saving 'store min only when it changes' or 'encode deltas' optimizations.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewNeed O(1) getMin on a stackMin changes as elements pop?YesExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Single Min Variable / Scan on getMin (Baseline)
1Hint 1Explore hintHide hint
The easy part is push/pop/top on a normal stack. What goes wrong if you keep just one variable for the current minimum?
2Hint 2Explore hintHide hint
A lone min variable can't tell you what the minimum was BEFORE the current min was pushed — once you pop it, the previous min is lost.
3Hint 3Explore hintHide hint
You could rescan the whole stack for getMin instead, but that's O(n) and violates the requirement.
A · Core idea
Use a normal stack and either (a) keep one min field, or (b) scan the stack on each getMin.
Why it fails. Option (a) breaks on pop: suppose you push 5 then 1 — min is 1. Pop the 1 and the true minimum is 5 again, but a single variable has no memory of the previous minimum, so it stays 1 (wrong). Option (b) is correct but getMin becomes O(n), violating the constant-time requirement. Both shortcomings point to the same fix: we must remember the minimum at every level, not just the latest, so popping can roll it back.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
push(5), push(1), pop(), getMin()push(5)
min = 5
min=5incorrectC · Code with teaching comments
Reference implementation previewclass MinStackNaive { private Deque<Integer> stack = new ArrayDeque<>(); private int min = Integer.MAX_VALUE; public void push(int val) {Explore the full codeHide the full code
D · Why it works and complexityO(1) push/pop/top, O(n) getMin timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The minimum is a stack-structured quantity itself — it changes and must be undone in LIFO order exactly like the data. That's the clue that the minimum belongs on its own stack.
ComplexityO(1) push/pop/top, O(n) getMin time, O(n) space — A single min variable can't be restored on pop; recomputing min by scanning is O(n) per call.
E · Edge cases and pitfalls2A single min variable cannot recover the previous minimum after a pop.Explore edge casesHide edge cases
Pitfalls
- A single min variable cannot recover the previous minimum after a pop.
- Rescanning the stack for getMin is correct but O(n), breaking the constraint.
Approach 2 of 2 · Optimal
Parallel Min Stack (Optimal)
1Hint 1Explore hintHide hint
The minimum changes in LIFO order just like the data does. What structure naturally undoes its most recent change first?
2Hint 2Explore hintHide hint
Keep a second stack the same height as the main one; at each level store the minimum of all elements at or below that level.
3Hint 3Explore hintHide hint
On push, push min(val, currentMin). On pop, pop both. getMin is the top of the min stack — O(1).
A · Core idea
Maintain two stacks: stack for the values and mins where each entry is the minimum of all elements at or below that position.
push(val). Push val onto stack. Compute the new running minimum as min(val, currentMin) — where currentMin is the top of mins, or val itself if mins is empty — and push that onto mins. Now mins's top always equals the minimum of the entire current stack.
pop(). Pop from both stacks. This is the crux: removing the top mins entry exposes the entry beneath it, which was computed as the minimum of everything still in the stack. So the previous minimum is restored automatically — no recomputation, no scan. This is precisely what the single-variable approach couldn't do.
top() and getMin(). Both are O(1) peeks: top() reads the top of stack, getMin() reads the top of mins.
Why each level stores its own min. Because the minimum is itself a LIFO quantity — it can change on push and must roll back on pop — it deserves its own stack. By caching min(val, currentMin) at push time, we pre-pay the comparison once and make every future getMin a free peek, and we make every pop a free restore.
Duplicates and negatives. Pushing min(val, currentMin) handles duplicates correctly: pushing 2 twice puts 2 on mins twice, so popping one leaves a 2 and getMin stays 2. Full-range and negative values work since we only compare, never assume sign.
Space optimizations (worth mentioning). Two common reductions: (1) push onto mins only when val <= currentMin, and on pop only remove from mins when the popped value equals mins's top — saves space when minimums repeat rarely; (2) store deltas against the running min in a single stack, recovering O(1) min with no second stack at the cost of trickier arithmetic.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
push(-2), push(0), push(-3), getMin(), pop(), top(), getMin()Array state
push(-2)
stack & mins gain -2
getMin=-3, top=0, getMin=-2C · Code with teaching comments
D · Why it works and complexityO(1) for all operations timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
The reusable principle is 'carry the answer with the data so undo is free'. When a stack/queue must also answer an aggregate query in O(1), attach the aggregate to each element so the structure's own push/pop maintain it. The same idea builds a FIFO queue from two stacks and a max-stack by symmetry.
ComplexityO(1) for all operations time, O(n) space — push/pop/top/getMin each do a constant number of stack operations → O(1) each. The auxiliary stack mirrors the main one → O(n) extra space.
E · Edge cases and pitfalls8push(5) then getMin/top — First push: mins empty → currentMin = 5. getMin and top both return 5.Explore edge casesHide edge cases
Edge cases handled
push(5) then getMin/topFirst push: mins empty → currentMin = 5. getMin and top both return 5.push(2), push(2), pop(), getMin()mins = [2, 2]; popping leaves [2] → getMin stays 2 (duplicates handled).all negatives, e.g. push(-1), push(-5)min(-5, -1) = -5 pushed → getMin returns -5; signs irrelevant.push then pop down to empty, then push againmins empties with stack; next push treats val as its own min — no stale state.
Pitfalls
- Popping only one of the two stacks — they desync and getMin lies.
- On the first push, calling mins.peek() on an empty stack — guard with isEmpty().
- Storing only when val < currentMin without the matching conditional pop — under-pops the min stack.
- Recomputing min by scanning instead of peeking — silently O(n).
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewclass MinStack { private final Deque<Integer> stack = new ArrayDeque<>(); private final Deque<Integer> mins = new ArrayDeque<>(); public MinStack() {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Desynced stacks on pop
The two stacks must stay the same height. Popping only the value stack leaves a stale entry on the min stack, so getMin reports a minimum that's no longer present.
Explore the code comparisonHide the code comparison
Desynced stacks on pop
The two stacks must stay the same height. Popping only the value stack leaves a stale entry on the min stack, so getMin reports a minimum that's no longer present.
Don't write this
public void pop() {
stack.pop(); // forgot mins.pop()
}Write this instead
public void pop() {
stack.pop();
mins.pop();
}2Peeking an empty min stack on first push
On the very first push there is no current minimum yet. Calling mins.peek()/mins[-1] throws or errors. Treat the first value as its own minimum.
Explore the code comparisonHide the code comparison
Peeking an empty min stack on first push
On the very first push there is no current minimum yet. Calling mins.peek()/mins[-1] throws or errors. Treat the first value as its own minimum.
Don't write this
def push(self, val):
self.stack.append(val)
self.mins.append(min(val, self.mins[-1])) # IndexError when emptyWrite this instead
def push(self, val):
self.stack.append(val)
current_min = val if not self.mins else min(val, self.mins[-1])
self.mins.append(current_min)Revise
Carry the pattern into the interview
Pattern to remember
Auxiliary min stack tracking the minimum per level
Code pattern to memorize
push(x): stack.push(x); mins.push(min(x, mins.peek() if mins else x))
pop(): stack.pop(); mins.pop()
top(): return stack.peek()
getMin(): return mins.peek()Rules to remember
- 1Push to both stacks on every push, and pop from both on every pop — keep them the same height.
- 2The value pushed onto the min stack is
min(newValue, currentMin), so each level remembers the minimum of everything below it. - 3
getMin()andtop()are pure peeks — O(1), no scanning. - 4Popping the min stack automatically restores the previous minimum; that's why you never need to recompute it.
- 5Handle the empty case on the first push (no current min yet) — the first value is its own minimum.
Use this pattern when
- A stack that must also report its min (or max) in O(1)
- Any LIFO structure needing an O(1) aggregate (min/max) that must roll back on pop
- Design problems where 'support operation X in O(1)' is the explicit constraint
Choose another pattern when
- You need min over an arbitrary range, not the whole stack → segment tree / sparse table
- Min over a sliding window (FIFO) → monotonic deque, not a min stack
- You only ever read the min once at the end → just compute it then; no auxiliary structure
Interview takeaway
Carry the answer with the data: storing the running minimum at each stack level makes pop automatically undo it, trading O(n) extra space for O(1) queries. This 'remember the aggregate per level' idea also builds a queue from two stacks and underpins the two-stack max/min trick.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternStack design with an O(1) auxiliary aggregate. Recognise it when a LIFO structure must also answer min/max in constant time: store the aggregate per level so push maintains it and pop restores it. Related to two-stack queue and max-stack designs.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Implement Queue using StacksComing soonTwo stacks (in/out) give amortized O(1) FIFO — same 'carry state across stacks' idea.
- Max StackComing soonSymmetric to Min Stack, but popMax needs a more elaborate structure (e.g. balanced BST + stack).
- Implement Stack using QueuesComing soonThe reverse design exercise; one queue with rotation, or two queues.
- Valid ParenthesesThe canonical 'plain stack' problem this design extends.