Notes / Data Structures Algorithms / 04 Stack Queue And Deque / 1 Stack

1 — Stack

LIFO fundamentals: push/pop/peek in O(1), why list.append()/list.pop() are the right end and list.pop(0) isn't, the call stack as a literal stack, and worked bracket-matching and iterative-DFS examples.

Updated July 27, 2026 · §202607241159-19 ·

1 — Stack

A stack is the smallest data structure in this book, which is exactly why it earns a full chapter: the restriction is the design. It stores a sequence like a list does, but refuses to let you touch anything except one end — a guarantee, not a limitation, that turns out to make function calls, undo buffers, expression parsing, and depth-first traversal all work the same way underneath.


LIFO: Access Restricted to One End

A stack supports exactly three operations, and every one of them touches the same end — the top:

  • push(x) — add x to the top.
  • pop() — remove and return whatever is currently on top.
  • peek() (sometimes top()) — read the top value without removing it.

The ordering this produces is LIFO — last in, first out: whatever was pushed most recently comes back out first — the opposite of a queue (next chapter), which is FIFO. There is no pop(i) for an arbitrary index, no insert in the middle, no reading the third element from the bottom. That’s not an oversight — it’s the interface, and every algorithm below leans on it being true.


Implementing a Stack in Python

Python’s list is a stack once you commit to using only one end of it:

stack = []
stack.append(10)      # push
stack.append(20)      # push
stack.append(30)      # push

stack.pop()            # 30 — pop
stack[-1]              # 20 — peek, without removing

list.append() and list.pop() with no index argument are both O(1) amortized — the same amortized-doubling argument from Part 01 Chapter 2 and Part 02 Chapter 1 applies unmodified, because both operate on the end of the underlying array: append writes into reserved capacity (or triggers a geometric resize, amortized away over many pushes), and pop() decrements the length and returns the last slot. Neither moves any other element.

The gotcha: list.pop(0) and list.insert(0, x) look like reasonable stack operations if you picture the stack growing at index 0 — but they operate on the front, so every remaining element shifts by one position to close or open the gap. This is silent: the code runs, produces correct LIFO order, and quietly turns an O(1) algorithm into O(n²) the first time it’s called in a loop:

stack.insert(0, 5)     # O(n) — shifts every existing element right by one
stack.pop(0)           # O(n) — shifts every remaining element left by one

The fix is picking the end that’s actually cheap — the end — and never touching the other one. collections.deque supports push/pop/peek from either end in O(1), and is the safer default whenever the other end might be needed too — the next chapter’s queue and deque both need front-end operations that make plain list dangerous:

from collections import deque

stack = deque()
stack.append(10)       # push
stack.pop()             # 10 — pop from the same end you pushed to

The Call Stack Is a Literal Stack

Part 01, Chapter 3 built up the idea of a stack frame without naming the data structure directly: every recursive call gets “its own copy of the arguments, its own place to resume once the call it made returns,” and frames “stack up until a base case is hit, then unwind in reverse order.” Reread that with this chapter’s vocabulary and it’s an exact description of push and pop: every function call is a push — the interpreter allocates a new frame, records where to resume in the caller, and puts it on top of the call stack. Every return is a pop — the top frame is removed and control resumes exactly where the now-restored frame left off.

That’s not a teaching analogy — “the call stack” is the actual name of the actual stack the interpreter maintains, and factorial(4)’s traced output from the recursion chapter is a stack trace in the most literal sense:

call factorial(4)              push
  call factorial(3)            push
    call factorial(2)          push
      call factorial(1)        push
      base case: return 1      pop
    return factorial(2) = 2    pop
  return factorial(3) = 6      pop
return factorial(4) = 24       pop

Four pushes down, four pops back up, in exactly reversed order — LIFO, because factorial(4)’s frame can’t pop until factorial(3)’s does. This is also why RecursionError exists: sys.getrecursionlimit() caps how many pushes without a matching pop the interpreter allows before deciding the stack has grown too deep to be safe.


Worked Example: Valid Parentheses

The canonical stack interview problem: given a string of brackets — ()[]{} — determine whether every opening bracket is closed by the matching type, in the correct order.

Approach: walk the string once. Every opening bracket gets pushed. Every closing bracket pops the stack and checks that what came off matches the closer’s expected type. The string is valid iff the stack is completely empty exactly when the string ends — not before (an unmatched closer with nothing to pop) and not after (an unmatched opener still sitting on the stack).

def is_valid(s: str) -> bool:
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []

    for char in s:
        if char in pairs.values():          # opening bracket
            stack.append(char)
        elif char in pairs:                 # closing bracket
            if not stack or stack.pop() != pairs[char]:
                return False                # popped nothing, or wrong type
        # any other character is ignored here; real inputs are usually brackets-only

    return not stack                        # empty iff every opener was matched

is_valid("({[]})")   # True
is_valid("([)]")     # False — closes '(' with ')' before '[' is closed
is_valid("(()")      # False — one opener never gets popped

stack.pop() != pairs[char] is where LIFO earns its keep: the most recently opened bracket must be the next one closed — exactly the order a stack hands things back in. A queue (FIFO) would check the oldest unclosed bracket first, the wrong order for nested structure.


Worked Example: Iterative DFS

Part 01 Chapter 3 showed that any recursion can be mechanically rewritten with an explicit stack in place of the interpreter’s call stack (flattenflatten_iterative). Depth-first traversal of a tree or graph is the same conversion — worth seeing once here in its lightest form. Full graph DFS, with visited-sets and edge classification, is Part 06 (Graphs)‘s job, not this chapter’s.

Recursive DFS relies on the call stack implicitly — visit(node) then recurse into each child, the same shape as flatten. The iterative version makes that stack explicit and manages it by hand:

def dfs_iterative(root, visit):
    if root is None:
        return
    stack = [root]
    while stack:
        node = stack.pop()          # LIFO: most recently pushed child visited next
        visit(node)
        for child in reversed(node.children):
            stack.append(child)     # reversed() keeps left-to-right visit order

stack.pop() stands in for “the recursive call the interpreter would currently be inside” — the same substitution flatten_iterative made for nested lists. reversed(node.children) only matters for visit order: pushing left-to-right and popping from the top would visit the rightmost child first, since the last one pushed is the first one popped — reversing restores the recursive version’s natural left-to-right order.


Complexity Summary

OperationComplexityWhy
pushO(1)*Amortized — same doubling argument as dynamic arrays
popO(1)Removes from the end; no shifting
peekO(1)Reads the end directly
Search / access middleO(n)Must pop everything above the target to reach it

* An individual push that triggers a resize costs O(n) for that one call, but the cost averages to O(1) per push over any long sequence (Part 01 Ch2, Part 02 Ch1).

That last row isn’t a flaw to route around — it’s the whole point. A stack that let you reach into the middle in O(1) would just be a list wearing a stack’s name. Restricting access to one end is what makes push, pop, and peek unconditionally cheap — and what makes the call stack, bracket matching, and iterative DFS all reach for the same three operations.

Metadata

AuthorAmit Singh
Scopedata-structures-algorithms

Local graph

Full graph →

Linked from 7 notes

2 — Queue

FIFO fundamentals: enqueue/dequeue, why list.pop(0) is a silent O(n) trap, collections.deque as the fix, the two-stack amortized-O(1) implementation, and BFS as the queue's signature use case.

4 — Deque

Deque as the shared generalization behind stack and queue: O(1) push/pop at both ends via collections.deque's fixed-size-block internals (not a list, not a per-element linked list), worked rotate/maxlen/extendleft examples, and the O(n) random-access trade-off a list doesn't have to make.

5 — Monotonic Stack

A stack that enforces increasing or decreasing order at push time, solving next-greater-element, daily-temperatures, and largest-rectangle-style problems in O(n).

7 — Expression Evaluation

Infix vs. postfix vs. prefix notation, evaluating postfix/RPN with a single stack, the two-stack method for direct infix evaluation with operator precedence and parentheses, a full Basic-Calculator implementation, and shunting-yard as an alternative infix-to-postfix conversion strategy.

2 — Binary Trees

Binary tree node structure and traversal: preorder, inorder, and postorder — each recursive and iterative (postorder's two-stack trick for the trickiest case) — plus level-order BFS via a queue, recursive height/depth, and when each traversal order actually matters in practice.

2 — Graph Traversal

DFS and BFS generalized from trees to graphs via one addition — a visited set — plus recursive and iterative DFS, BFS's third appearance of the same queue skeleton, connected components, and multi-source BFS as single-source BFS from an imaginary super-source.

Data Structures & Algorithms

A book-shaped table of contents for MAANG-interview DSA prep: Python language foundations, mathematical and algorithmic foundations, arrays/strings, linked structures, stacks/queues, trees, graphs, sorting/searching, dynamic programming, greedy algorithms, backtracking, bit manipulation, advanced data structures, advanced algorithms, interview problem patterns, and MAANG interview mastery — a book-length progression from fundamentals to Google/Meta/Amazon/Apple/Netflix/Microsoft (L4–L6) interview readiness.