Algorithm

Implement Queue using Stacks

Design Pattern

Queue using Stacks

Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue: push, peek, pop, and empty. void push(int x) pushes element x to the back. int pop() removes and returns the front element. int peek() returns the front element. boolean empty() returns true if empty. Each operation must only use standard stack operations.

CONSTRAINTS
  • 1 <= x <= 9
  • At most 100 calls to push, pop, peek, and empty
  • All calls to pop and peek are valid (queue will not be empty)
EXAMPLE 1
Input: push(1), push(2), peek(), pop(), empty()
Output: 1, 1, false
1 and 2 arrive. peek returns the oldest, 1; pop also returns 1. 2 is still waiting, so empty is false. FIFO order is preserved.
EXAMPLE 2
Input: push(1), pop(), push(2), peek()
Output: 1, 2
1 arrives and is immediately popped → 1. Then 2 arrives and is the only element, so peek returns 2.
Why keep two stacks instead of reversing one in place each time?
Two stacks let the reversal be lazy and shared: items reversed once into the outStack stay reversed until served. Reversing a single stack on every operation would redo the same work repeatedly, making each call O(N).
When exactly is a transfer allowed?
Only when the outStack is empty. If it still holds items, its top is already the correct front — transferring would re-scramble order and waste work.
The problem says pop/peek are always valid — do I still handle empty?
Not strictly required by the constraints, but a robust empty() (true only when both stacks are empty) is cheap and worth having.

Two structures collide here, so start by pinning down what each one is. A stack is a pile you touch only from the top: push sets a value on top, pop removes the top one — the last thing in is the first out (LIFO). A queue is a line at a checkout: you join at the back and leave from the front — the first thing in is the first out (FIFO). The problem hands us only stacks and asks us to behave like a queue. The two orderings are exact opposites, which is precisely the crack we can exploit.

The one fact everything rests on: a stack reverses

Pour one stack into another and the order flips. Push 1, 2, 3 onto stack A (top is 3). Now repeatedly pop A and push each into stack B: B receives 3, then 2, then 1, so B's top is 1. The element that went into A first (the 1, buried at A's bottom) is now sitting on top of B, first to come out. That is exactly the queue's front. So a single reversal turns LIFO into FIFO. The whole design is: arrivals pile up in one stack, and when we need to serve someone, a reversal exposes the oldest.

Two stacks with distinct jobs

Keep an inStack for arrivals and an outStack for departures. New items always push onto inStack. To pop or peek the queue's front, we need the oldest item — which lives at the bottom of inStack, unreachable directly. So we pour inStack into outStack (the reversal), and now the oldest sits on top of outStack, ready to serve.

The one subtlety that makes this efficient: only pour when outStack is empty. As long as outStack still has items, its top is already the correct next-to-serve — don't touch inStack. New arrivals keep stacking on inStack meanwhile. We refill outStack only once it runs dry, in one batch.

python
class MyQueue:
    def __init__(self):
        self.in_stack = []
        self.out_stack = []

    def push(self, x):
        self.in_stack.append(x)          # arrivals always go here

    def _transfer(self):
        if not self.out_stack:           # ONLY refill when empty
            while self.in_stack:
                self.out_stack.append(self.in_stack.pop())

    def pop(self):
        self._transfer()
        return self.out_stack.pop()      # oldest is on top after reversal

    def peek(self):
        self._transfer()
        return self.out_stack[-1]

    def empty(self):
        return not self.in_stack and not self.out_stack
Crucial Notethe if not self.out_stack guard is the entire trick — do not pour on every pop. If you transferred every time, items already reversed into outStack would get poured back into inStack and re-reversed, scrambling the order and wasting work. Pour only into an empty outStack, drain it completely before refilling, and the order is always correct.
Why it's O(1) amortized, not O(N)

A single pop that triggers a transfer of N items looks like O(N) — and occasionally it is. But track any one element's whole life: it is pushed onto inStack once, popped from inStack once, pushed onto outStack once, popped from outStack once. Four touches, forever — no element is ever moved twice by the same transfer, because we drain outStack fully before refilling. Spread the cost of the occasional expensive transfer across all the cheap operations that element also took part in, and the average cost per operation is constant. That averaged-out cost is what "amortized O(1)" means: not every call is O(1), but any long run of calls totals O(1) each.

Worked Example:interleave arrivals and departures
- push(1), push(2) → inStack [1, 2] (top 2), outStack [].
- peek() → outStack empty, so transfer: pop 2→push, pop 1→push. Now outStack [2, 1] (top 1). Return 1 (the oldest, correctly).
- push(3) → goes onto inStack: inStack [3], outStack [2, 1]. Note we did not disturb outStack.
- pop() → outStack non-empty, so no transfer; pop its top → 1. outStack [2].
- pop() → still non-empty; pop → 2. outStack [].
- pop() → outStack now empty, transfer the waiting 3: outStack [3]; pop → 3. Served order 1, 2, 3 — true FIFO.

Train the reflex: when you're handed one structure and asked to mimic its opposite, look for a reversal the target ordering wants, and delay that reversal until it's actually needed. The mirror version of this exact idea — simulating a stack out of queues — is the very next problem.

Interactive Strategy Visualization
QUEUE EMULATION INSIGHT

Bridging LIFO and FIFO with Dual Stacks

S1: Inbox
S2: Outbox

The Reversal Insight

A single stack reverses order (LIFO). By transferring to a second stack, we reverse the reversal, effectively yielding the original order (FIFO).

AMORTIZED O(1)Each element is pushed/popped between stacks exactly twice. Average cost per operation remains constant.
Operation1 / 6
[push(1)]Push 1 to s1.

Strategy: Lazy Transfer

Don't move elements unnecessarily. Only transfer from Inbox (S1) to Outbox (S2) when S2 is empty and a Pop/Peek is requested.

O(N) Reverse Every Pop
O(1) Amortized Lazy Two-Stack Transfer