1. What It Is and Why It Exists

A is a LIFO (last-in, first-out) collection: you can only add (push) and remove (pop) at one end, the top. A is FIFO (first-in, first-out): you add at the back (enqueue) and remove from the front (dequeue). Both restrict where you can touch the data, and that restriction is exactly the point — it makes the structure model a specific control flow in O(1) per operation. A mirrors nested/recursive structure (matching brackets, function call frames, "undo", depth-first search or ). A mirrors order-preserving processing (breadth-first search or , scheduling, buffering). follows a branch before returning to others; visits nodes in successive distance layers.

The "why it works" intuition: a stack is the call stack — anything naturally recursive can be made iterative with an explicit stack, which is how you avoid -depth limits in an interview. A queue guarantees you process items in arrival order, which is precisely why BFS finds shortest paths in an unweighted graph: nodes come off the queue in non-decreasing distance order. Without these structures you'd reimplement the same ordering bookkeeping by hand every time.

A FIFO queue keeps BFS in distance order

BFS removes nodes in non-decreasing distance order, so its first discovery gives a shortest path in an unweighted graph.

Start at A; the queue removes from the front and adds at the back.

Text version
  1. Start at A; the queue removes from the front and adds at the back.
  2. Visit A and enqueue B and C at distance one.
  3. Dequeue B, then add D at distance two.
  4. Dequeue C; D is already queued, so only E joins at distance two.
  5. Dequeue D, then add F at distance three.
  6. Dequeue E; F is already queued, so nothing new joins.
  7. Dequeue F; the queue is empty and the search is done.
  8. FIFO preserves distance layers, so first discovery gives a shortest path in this unweighted graph.
Quick check

A web crawler needs to explore all pages on a website by visiting pages level-by-level — first all pages one link away from the start, then all pages two links away, and so on — to find the shortest click-path between two pages. Which data structure should hold the pages waiting to be visited, and why?

Choose one answer

2. Operations and Complexity

StructureOperationAverageWorstNotes
Stack (list)push / appendO(1) amortizedO(n)Resize amortizes away
Stack (list)pop endO(1)O(1)—
Stack (list)peek (a[-1])O(1)O(1)—
Queue (deque)append (enqueue)O(1)O(1)Both ends are true O(1)
Queue (deque)popleft (dequeue)O(1)O(1)—
Queue (list) ❌pop(0) (dequeue)O(n)O(n)Shifts every element — never do this

Space is O(n). The important distinction is cost: a Python list can implement a , but removing from its front is slow. list.pop(0) is O(n), so draining n items this way costs O(n^2). The expected answer is collections.deque for queues (and stacks too). Interviewers also probe whether you can implement a with two stacks (amortized O(1)).

The cost of draining a queue

Repeated list.pop(0) calls have quadratic total cost; constant-time deque removals give linear total cost.

Deque removal takes constant time, so draining n items has linear cost.

Text version
  1. Deque removal takes constant time, so draining n items has linear cost.
  2. Removing the first list item shifts the remaining items, so one removal takes linear time.
  3. Repeating that removal to drain the list gives quadratic total cost.
  4. As the input grows to sixty-four items, n reaches sixty-four while n squared reaches four thousand ninety-six.
  5. Use collections dot deque for queues, because its end operations avoid those repeated shifts.
Quick check

A backend service processes incoming API requests in the order they arrive, using a Python list and calling list.pop(0) to dequeue each request. Under high load with thousands of requests queued, what is the per-dequeue time complexity, and what should you use instead?

Choose one answer

3. Implementation

The wrapper below exposes last-in, first-out operations. The uses two stacks: moving all items from _in to _out reverses their order, putting the oldest item on top. Each item moves only once, so a sequence of operations costs amortized O(1) per operation even though one transfer can cost O(n).

class Stack:                              # LIFO; one end only
    def __init__(self):
        self._data = []
    def push(self, x):  self._data.append(x)        # O(1) amortized
    def pop(self):      return self._data.pop()     # O(1)
    def peek(self):     return self._data[-1]       # O(1)
    def empty(self):    return not self._data

class Queue:                              # FIFO via two stacks: amortized O(1)
    def __init__(self):
        self._in = []                     # push here
        self._out = []                    # pop from here
    def enqueue(self, x):
        self._in.append(x)                # O(1)
    def dequeue(self):
        if not self._out:                 # move only when out is empty:
            while self._in:               # each element moves at most once
                self._out.append(self._in.pop())  # -> amortized O(1)
        return self._out.pop()

In a real interview:

  • → use a Python list (append / pop / a[-1]).
  • Queue → use collections.deque (append / popleft), never list.pop(0).
  • Need both ends or a sliding-window queue → collections.deque (it is the canonical monotonic-queue backbone).
from collections import deque
stack = []                 # push: stack.append(x); pop: stack.pop()
queue = deque()            # enqueue: queue.append(x); dequeue: queue.popleft()

Two stacks preserve arrival order

Refill out only when it is empty; reversing in puts the oldest item on top, and each item transfers at most once.

Both stacks start empty, with their tops on the right.

Text version
  1. Both stacks start empty, with their tops on the right.
  2. Enqueue one, two, then three by pushing each onto in.
  3. Out is empty, so dequeue must refill it before returning an item.
  4. Three moves first, from the top of in to out.
  5. Two and then one follow, leaving the oldest item on top of out.
  6. Pop out and return one, the first item enqueued.
  7. Enqueue four by pushing it onto in.
  8. Out still has items, so this dequeue skips the transfer.
  9. Pop out and return two, preserving arrival order.
  10. Each item transfers at most once, giving amortized constant time even though one transfer can take linear time.
Quick check

A backend service needs to process incoming HTTP requests in the order they arrive, with high throughput. A developer proposes using a Python list and calling list.pop(0) to dequeue each request. What is the key problem with this approach, and what should be used instead?

Choose one answer

4. When to Reach for It

  • : matching/nesting (parentheses, valid-expression), "most recent unmatched", undo/, converting (, tree traversal) to iteration, and the entire monotonic- pattern (next greater element).
  • : / level-order traversal, shortest path in an unweighted graph, scheduling and rate-limiting, and the monotonic-deque sliding-window-maximum pattern.
  • Deque: any time you need O(1) operations at both ends, or a fixed-size .
Quick check

You are building a feature that finds the shortest path between two users in a social network graph where all connections have equal weight. Which data structure should drive your traversal, and why?

Choose one answer

5. Common Pitfalls

  • Using list.pop(0) as a dequeue: O(n) each → O(n^2) total. Always collections.deque.popleft().
  • Popping/peeking an empty or : raises IndexError. Guard with an emptiness check before every pop/peek.
  • LIFO vs FIFO mix-up: using a where order must be preserved (or a queue for nesting) gives subtly wrong output that passes tiny tests and fails larger ones.
  • deque random indexing: dq[i] for a middle index is O(n) — deque is fast only at the ends; use a list if you need indexing.
  • Two-stack queue: transferring every call. Only refill out when it is empty; transferring while out still holds items puts newer items above older ones and breaks FIFO order.
Quick check

A backend service implements a two-stack queue to process incoming API requests in FIFO order. A developer writes the dequeue operation so that every call moves all elements from the in stack to the out stack, processes one element, then moves everything back. What is the practical consequence of this approach compared to only refilling out when it is empty?

Choose one answer

6. Interview Cheat Sheet

  • " is LIFO, is FIFO. A deque has O(1) end operations; the list and two-stack here have amortized O(1) operations."
  • "In Python a list is my stack; a deque is my queue — list.pop(0) is O(n), so I never use a list as a queue."
  • "A stack converts any to iteration and avoids -depth limits; a queue gives its shortest-path-in-unweighted-graph property."
  • "I can build a queue from two stacks at amortized O(1) by only refilling the output stack when it's empty."

Follow-ups:

  • "Implement a queue using stacks." — Two stacks; push to in, pop from out, refill out from in only when out is empty → amortized O(1).
  • "Implement a stack using queues." — One queue, on push rotate the queue so the new element is at the front → O(n) push, O(1) pop (or the reverse).
  • "Min-stack / get-min in O(1)?" — Keep a second stack of running minimums (or store (value, min_so_far) pairs); every operation stays O(1).

Reading

On this page

My notes

Sign in to keep highlights and notes.

Glossary history

Click dotted jargon to save explanations here.