Stacks and Queues
4 min read
Dsa Index
Dsa Index
Stacks and Queues
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", ). A mirrors order-preserving processing ( level order, scheduling, buffering).
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 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 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?
2. Operations and Complexity
| Structure | Operation | Average | Worst | Notes |
|---|---|---|---|---|
Stack (list) | push / append | O(1) amortized | O(n) | Resize amortizes away |
Stack (list) | pop end | O(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 single most-probed fact: a Python list is a correct but a broken — list.pop(0) is O(n), so a built on a list is O(n) per dequeue and O(n^2) overall. The expected answer is collections.deque for queues (and stacks too). Interviewers also probe whether you can implement a queue with two stacks (amortized O(1)).
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?
3. Implementation
From scratch, then the stdlib you'd actually use:
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]). - → use
collections.deque(append/popleft), neverlist.pop(0). - Need both ends or a sliding-window →
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()
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?
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 .
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?
5. Common Pitfalls
- Using
list.pop(0)as a dequeue:O(n)each →O(n^2)total. Alwayscollections.deque.popleft(). - Popping/peeking an empty or : raises
IndexError. Guard with an emptiness check before everypop/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.
dequerandom indexing:dq[i]for a middle index isO(n)— deque is fast only at the ends; use alistif you need indexing.- Two-stack queue: transferring every call. Only refill
outwhen it is empty; transferring on every dequeue destroys the amortizedO(1).
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?
6. Interview Cheat Sheet
- " is LIFO, is FIFO; both are
O(1)per operation — the restriction is the feature." - "In Python a
listis my ; adequeis my —list.pop(0)isO(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 fromout, refilloutfrominonly whenoutis empty → amortizedO(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 staysO(1).
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.