Queue Using Stacks
Use two stacks: push to stack1 (inbox). When you need to dequeue, if stack2 (outbox) is empty, pour everything from stack1 into stack2 (reversing the order). Then pop from stack2. Amortized O(1) per operation.
How It Works
Two LIFO stacks compose into one FIFO queue because two reversals restore original order. Pushes go onto an inbox stack. To dequeue or peek, pop from an outbox stack; if the outbox is empty, first pour the entire inbox into it, which reverses the elements so the oldest sits on top. Crucially, transfer only when the outbox runs dry — the outbox already holds older elements in correct order, and pouring early would interleave them wrongly.
A single dequeue can cost O(n) during a transfer, but each element is moved exactly once in its lifetime: pushed to the inbox, poured once, popped from the outbox. Total work over any n operations is O(n), so every operation is amortized O(1).
Step-by-Step Visualization
Code
class QueueUsingStacks {
Stack<Integer> inbox = new Stack<>();
Stack<Integer> outbox = new Stack<>();
void push(int x) { inbox.push(x); }
int pop() {
if (outbox.isEmpty())
while (!inbox.isEmpty()) outbox.push(inbox.pop());
return outbox.pop();
}
int peek() {
if (outbox.isEmpty())
while (!inbox.isEmpty()) outbox.push(inbox.pop());
return outbox.peek();
}
}Tips & Gotchas
Practice Problems
- 1Implement Queue using Stacks
- 2Implement Stack using Queues
- 3Design Circular Queue
About the Queue Design Pattern
Classic design problems that test your understanding of how queues work internally.
BFS = queue. If you need shortest path in an unweighted graph or level-order traversal, reach for a queue. Monotonic deques solve sliding window extremes in O(n).
Common Queue / Deque Interview Problems
- Binary Tree Level Order Traversal
- Sliding Window Maximum
- Rotting Oranges
- Shortest Path in Binary Matrix
- Implement Queue using Stacks
Frequently Asked Questions
Why is amortized O(1) an acceptable answer when one call can take O(n)?
Amortized analysis bounds the average over any operation sequence, and here each element pays for its own single transfer. Unless the problem demands strict worst-case latency — as some real-time systems do — the amortized bound is the standard and expected answer.
What is the classic mistake in this design?
Pouring the inbox into the outbox on every dequeue, or whenever the inbox is non-empty. That both breaks FIFO order when the outbox still holds older elements and destroys the amortized bound by moving elements repeatedly. Transfer only when the outbox is empty.
How does the reverse problem, a stack from queues, differ?
It is inherently costlier with plain FIFO queues: one operation (push or pop) must rotate up to n−1 elements to expose the newest, giving O(n) for that operation. The two-stack queue is the direction where amortized O(1) is achievable.