Run it
FIFO through interleaved operations, which is the part worth checking.
The cost of the emptiness check, measured on the same workload.
One dequeue can still be O(n) - amortised is a claim about the total.
Why reversing twice gives FIFO
A stack reverses what you put into it. Pour one stack into another and you have reversed it again, which restores the original order — so the item pushed first ends up on top of the second stack, which is exactly the front of a queue.
That is the whole trick, and it is worth stating in one sentence in an interview: the inbox has them newest-first, the outbox has them oldest-first, and pouring is what converts between the two.
The emptiness check is the entire complexity argument
The naive version pours on every dequeue — and, to keep enqueue working, pours back afterwards. That is O(n) per call and O(n²) for n dequeues. The editor below measures it: 400 operations cost 160,000 moves that way, against 400 the right way.
Pouring only when the outbox is empty means each element crosses once in its entire life. Total moves for n items is n, so the average cost per dequeue is constant even though one individual dequeue can touch every element. That is amortised O(1), and the distinction between "amortised O(1)" and "O(1) worst case" is what the follow-up question is about.
When amortised is not good enough
Amortised bounds are about totals, and some systems care about the worst single call. A real-time audio callback or a request with a latency budget cannot afford the one dequeue that moves ten thousand items, even if the average is fine.
The honest answer is that this structure cannot fix that — and naming the alternatives is what a strong answer does. Move a constant number of items per operation instead of all of them (incremental rebuilding), or use a structure that is O(1) worst case outright, which in Python is collections.deque. Which leads to the real-world footnote: nobody builds a queue from two stacks in production. The question is about reasoning, and it is fair to say so while still answering it.
The mirror question
"Now implement a stack using two queues." It is the same idea and strictly worse: one of the two operations has to become O(n), because a queue gives you the wrong end and no amount of shuffling amortises away. Either push moves everything into the second queue behind the new item, or pop moves n−1 items across to reach the last one.
There is no trick that makes both O(1) here, and saying that directly is the right answer — the asymmetry between the two questions is the thing being tested.
What to say out loud
One stack for incoming, one for outgoing. Enqueue pushes onto the inbox. Dequeue pops the outbox, and if the outbox is empty first it moves everything across, which reverses the order into FIFO. Enqueue is O(1); dequeue is O(1) amortised, because each element moves between the stacks exactly once in its lifetime, even though a single dequeue can be O(n).
Edge cases to raise
Volunteering these is most of what separates a correct answer from a good one.
Dequeue from empty. The outbox is empty and so is the inbox, so the pour moves nothing and the pop raises. Check it explicitly rather than letting an IndexError escape from an internal list.
peek() needs the same pour. Forgetting the emptiness check there is the standard bug in the extension, and it returns the newest item instead of the oldest.
Nobody ships this. collections.deque is O(1) at both ends and is what production code uses. Saying that while still answering the question shows you know why it is being asked.
The follow-ups interviewers ask
"What is the worst case for a single dequeue?" O(n) - the call that finds an empty outbox moves everything. The answer they want is the distinction: amortised O(1) is a statement about the total across n operations, not about any one of them.
"Now implement a stack from two queues." The mirror, and strictly worse: one of the two operations has to be O(n), because a queue hands you the wrong end and no shuffling amortises that away. Saying so directly is the right answer.
"How would you make dequeue O(1) worst case?" Not with this structure. Either move a constant number of items per operation (incremental rebuilding, which keeps both stacks partially filled), or use a structure that is O(1) outright - collections.deque, which is a doubly-linked list of blocks.
Common wrong answers
"Pour on every dequeue so the outbox is always current." Correct output, O(n) per call, O(n2) overall. The editor measures it as 160,000 moves against 400 for the same workload.
"Amortised O(1) means every operation is O(1)." It means the total for n operations is O(n). One call can still touch every element, which matters under a latency budget.
"Use one stack and reverse it when needed." Reversing is O(n) and you have to reverse back to keep pushing cheap - which is the eager version with extra steps.
Recap in one screen
- Enqueue never reorders anything - it is always one push.
- The pour happens only when the outbox is empty, and it reverses the order.
- While the outbox has items, dequeue does no moving at all.
- Worth trying: Add peek(). It needs the same emptiness check as dequeue, and forgetting it is the standard bug in this extension.
- Worth trying: Try implementing a stack from two queues and count the moves. One of the two operations stays O(n) however you arrange it, which is the asymmetry worth being able to explain.