Designing with stacks (min stack, queue via stacks)
In one line
Design questions ask you to add a feature (like get_min in O(1)) or build one structure from another (a queue from two stacks); the trick is storing a little extra state per element or moving elements lazily so every operation stays O(1) or O(1) amortised.
What it is
A min stack supports push, pop, top and get_min, all in O(1). Scanning for the minimum would be O(n), so instead each entry remembers the minimum of everything at or below it. When you pop, the minimum of the rest is already stored in the new top.
A queue built from two stacks uses an inbox for pushes and an outbox for pops. When the outbox is empty, move everything from the inbox to the outbox, which reverses the order so the oldest item ends up on top. Each element is moved at most once, so pushes and pops are O(1) amortised.
Why interviewers ask
Min Stack and Implement Queue using Stacks are classic design questions. They check whether you can reason about invariants (what is always true about your data), amortised complexity, and edge cases like popping from an empty structure. They also lead into real system ideas such as undo/redo and caching design.
How it works
- 1Min stack: push (x, min(x, current min)). get_min reads the min stored at the top. pop removes the pair, so the previous minimum comes back automatically.
- 2Space-saving variant: keep a second stack that only stores a value when it is less than or equal to the current minimum, and pop it when the popped value equals it. Using 'less than or equal' handles duplicates of the minimum.
- 3Queue via two stacks: push always goes to inbox. pop and peek use outbox; if the outbox is empty, pop everything from inbox into outbox first.
- 4Why amortised O(1): every element is pushed to the inbox once, moved to the outbox once, and popped once, about 3 operations in total, even though a single pop may move many elements.
- 5Stack via queues (the reverse question): after enqueueing x, rotate the queue by dequeuing and re-enqueueing the other n - 1 elements, so x is at the front. Push is O(n), pop is O(1).
Worked example · Tracing a min stack and a two-stack queue
- Min stack: push 5 -> [(5, 5)]. push 3 -> [(5, 5), (3, 3)]. push 7 -> [..., (7, 3)]. get_min = 3.
- pop removes (7, 3); get_min still 3. pop removes (3, 3); get_min = 5, read straight from the new top.
- Queue: push 1, 2, 3 -> inbox [1, 2, 3], outbox []. pop: outbox is empty, move all: outbox [3, 2, 1]; pop returns 1.
- push 4 -> inbox [4], outbox [3, 2]. pop returns 2 from the outbox, no moving needed. pop returns 3.
- pop: outbox empty, move 4 across, return 4. The order 1, 2, 3, 4 is first in, first out.
Picture it
A min stack is like a pile of exam papers where each paper has a sticky note with the lowest score in the pile so far: taking the top paper off reveals the right note underneath. The two-stack queue is like a pile of letters you flip over into a tray only when the tray is empty, so the oldest letter is on top.
Reference code
class MinStack:
def __init__(self):
self.items = [] # pairs: (value, min so far)
def push(self, x):
low = min(x, self.items[-1][1]) if self.items else x
self.items.append((x, low))
def pop(self):
return self.items.pop()[0]
def top(self):
return self.items[-1][0]
def get_min(self):
return self.items[-1][1]
class MyQueue:
def __init__(self):
self.inbox, self.outbox = [], []
def push(self, x):
self.inbox.append(x)
def peek(self):
if not self.outbox: # refill only when empty
while self.inbox:
self.outbox.append(self.inbox.pop())
return self.outbox[-1]
def pop(self):
self.peek()
return self.outbox.pop()
def empty(self):
return not self.inbox and not self.outbox
MinStack: all operations O(1), space O(n) for the pairs. MyQueue: push O(1); pop and peek O(1) amortised, O(n) worst case for a single call that refills the outbox. Callers should check empty() before pop or peek.
Key patterns
Store extra state per element
When: Min stack, max stack, stack with running sum
How: Push (value, aggregate so far); pop restores the previous aggregate automatically.
Lazy transfer between two stacks
When: Queue from stacks, amortised O(1) operations
How: Push to inbox; move everything to outbox only when it is empty.
Two stacks for two directions
When: Undo/redo, browser back/forward, text editor cursor (left and right of the cursor)
How: Moving between states pops from one stack and pushes to the other.
Stacks of stacks
When: Maximum frequency stack (LeetCode 895), set of stacks with a capacity
How: Map frequency to a stack of values; pop from the stack of the highest frequency.
Real-world use
Undo and redo
Editors such as VS Code, Google Docs and Photoshop's history use two stacks: actions go on the undo stack, undoing moves them to the redo stack, and a new action clears the redo stack.
Browser back and forward
Back and forward navigation is a pair of stacks: Back moves the current page to the forward stack, and visiting a new page clears it.
Functional and persistent queues
Functional languages such as Haskell, Clojure and OCaml implement immutable queues with two lists, front and back, reversing the back list when the front runs out: the same idea as a queue from two stacks.
Database savepoints
PostgreSQL and MySQL let a transaction set nested SAVEPOINTs; ROLLBACK TO SAVEPOINT discards the work done after it and any newer savepoints, so savepoints behave like a stack of checkpoints.
In a project
You are adding undo and redo to a collaborative whiteboard app, and product also wants a 'smallest selected font size' indicator that updates instantly as users undo changes.
- Represent each user action (draw, move, change font) as a command object with apply and revert methods.
- On a new action, apply it, push it on the undo stack and clear the redo stack.
- On undo, pop from the undo stack, revert it and push it on the redo stack; redo does the reverse.
- Store with each undo entry the minimum font size after that action, like a min stack, so the indicator is read from the top in O(1).
- Cap the undo stack at, say, 200 entries by dropping the oldest, using a deque.
Impact: Undo, redo and the font indicator are all O(1), so the UI stays instant even after thousands of edits, and memory is bounded by the cap.
Trade-offs
Use it when
- You need an aggregate (min, max, sum) of a stack at any moment in O(1)
- Only stacks are available (an interview constraint, or functional/immutable data) but you need FIFO
- Undo/redo or back/forward navigation
Avoid it when
- You need the minimum of a queue or sliding window: use a monotonic deque instead
- You need strict O(1) worst case per operation (real-time systems): two-stack queues have occasional O(n) refills
- You need arbitrary deletes or the minimum after removals from the middle: use a heap with lazy deletion or a balanced tree
Cost: Min stack: O(1) for every operation, O(n) space. Queue via two stacks: O(1) amortised per operation, O(n) worst case for one pop, O(n) space.
Common mistakes
- Storing only one global minimum, which becomes wrong after the minimum is popped.
- In the two-stack min variant, using strict 'less than', so duplicates of the minimum are lost after one pop.
- Moving elements from inbox to outbox on every pop even when the outbox is not empty, which breaks FIFO order.
- Calling the two-stack queue O(n) per pop without explaining amortisation, or claiming worst-case O(1).
- Not defining behaviour for pop or get_min on an empty structure.
Interview questions
Quick revision
- Min stack: push (x, min(x, top_min)); get_min = top's min.
- Two-stack min variant: push to min stack when x <= current min.
- Queue via stacks: push to inbox; pop from outbox; refill only when outbox empty.
- Each element moved once: O(1) amortised.
- Stack via queue: rotate after push, push O(n).
- Undo/redo = two stacks; new action clears redo.
- Always define empty-structure behaviour.
Check yourself
1. Min stack: push 4, push 2, push 6, push 1, pop, pop. What is get_min?
2. Queue via two stacks: push 1, push 2, pop, push 3, pop. What does the second pop return?
3. What is the amortised cost per operation of the two-stack queue?
4. In the two-stack min stack variant, why push to the min stack when x is equal to the current minimum?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.