Stacks and Queues

4 min read

Reading Progress0%
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.

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 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)).

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

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), never list.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()
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 on every dequeue destroys the amortized O(1).
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; both are O(1) per operation — the restriction is the feature."
  • "In Python a list is my ; a deque is my 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).