Data Structures

Stack

Last in, first out — the structure your function calls live on

A stack is a linear data structure where the last item added is the first one removed (LIFO). Push, pop, and peek all run in O(1). Stacks are how function calls track their state, how compilers parse expressions, and how undo histories work — they're the most-used hidden data structure in computing.

  • Push (insert at top)O(1)
  • Pop (remove from top)O(1)
  • Peek (read top)O(1)
  • Search by valueO(n)
  • SpaceO(n)
  • OrderingLIFO

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

How a stack works

Three operations, all touching the top:

  • Push — add an item to the top.
  • Pop — remove and return the top item.
  • Peek — return the top item without removing it.

That's it. No random access. No insertion in the middle. The discipline is what makes the stack useful — every operation is O(1) precisely because there's no choice about where in the structure to act.

Internally, a stack is usually a dynamic array with a top index pointing to the next empty slot, or a singly linked list with the head as the top. Both are O(1) for push and pop; the array version uses less memory and is faster in practice (cache locality), the linked-list version avoids occasional resize spikes.

Array-backed vs linked-list-backed

Array-backedLinked-list-backed
Push (amortized)O(1)O(1)
Push (worst case)O(n) on resizeO(1)
Memory per elementValue onlyValue + 1 pointer
Cache localityExcellentPoor
Memory layoutContiguousScattered
Best forAlmost all real workloadsReal-time / no resize spike

Modern standard libraries default to array-backed: Python's list + append/pop, Java's ArrayDeque, C++'s std::stack (which adapts std::deque by default). Java's older Stack class is array-backed but synchronized and avoided in modern code.

When to use a stack

  • Function call tracking. Every language runtime uses a stack. When you call f(), the runtime pushes a frame; when f returns, it pops the frame and resumes the caller.
  • Expression parsing and evaluation. Shunting-yard converts infix to postfix; postfix is then evaluated with a single operand stack.
  • Backtracking. DFS uses an explicit stack (or implicit via recursion) to remember where to back up to. Maze solvers, sudoku solvers, and constraint satisfaction all push state on entry and pop on backtrack.
  • Undo histories. Each user action pushes a state snapshot; undo pops the last one.
  • Bracket / tag matching. Any matching-pair problem is a stack problem: parentheses, HTML/XML tags, BBCode, function call/return.

Stack vs queue

Stack (LIFO)Queue (FIFO)
Insert locationTopRear
Remove locationTopFront
Order outReverse of insertSame as insert
Used forDFS, parsing, undo, recursionBFS, scheduling, message buffers
Real-world analogyPlate stack, browser back buttonLine at a coffee shop, print queue
ImplementationDynamic array, linked listCircular buffer, two stacks, deque

Pseudo-code

structure Stack:
    items[] = []

    push(x):
        items.append(x)

    pop():
        if items.length == 0: error("underflow")
        return items.removeLast()

    peek():
        if items.length == 0: error("empty")
        return items[items.length - 1]

    isEmpty():
        return items.length == 0

JavaScript implementation

class Stack {
  constructor() { this.items = []; }
  push(x)    { this.items.push(x); }
  pop()      { if (!this.items.length) throw new Error('underflow'); return this.items.pop(); }
  peek()     { if (!this.items.length) throw new Error('empty');     return this.items[this.items.length - 1]; }
  isEmpty()  { return this.items.length === 0; }
  get size() { return this.items.length; }
}

JavaScript arrays are already amortized-O(1) for push/pop on the end, so for most practical purposes a plain array IS a stack — no wrapper class needed.

Python implementation

class Stack:
    def __init__(self):
        self.items = []
    def push(self, x):       self.items.append(x)
    def pop(self):
        if not self.items: raise IndexError('underflow')
        return self.items.pop()
    def peek(self):
        if not self.items: raise IndexError('empty')
        return self.items[-1]
    def is_empty(self):      return not self.items

For real Python code, just use a list with .append() and .pop() — same O(1) behavior, no wrapper needed. collections.deque works too if you also want to access the bottom.

Famous stack problems

Valid Parentheses

Given a string of brackets, return true if every opener has a matching closer in the right order. The textbook stack problem.

function isValid(s) {
  const pairs = { ')': '(', ']': '[', '}': '{' };
  const stack = [];
  for (const ch of s) {
    if (ch === '(' || ch === '[' || ch === '{') stack.push(ch);
    else if (stack.pop() !== pairs[ch]) return false;
  }
  return stack.length === 0;
}

Min Stack — O(1) push, pop, peek, AND get-minimum

The trick: maintain a parallel "min so far" stack alongside the main stack. Every push records the running minimum; pop discards both.

class MinStack {
  constructor() { this.s = []; this.mins = []; }
  push(x) {
    this.s.push(x);
    this.mins.push(this.mins.length ? Math.min(x, this.mins[this.mins.length - 1]) : x);
  }
  pop()    { this.mins.pop(); return this.s.pop(); }
  top()    { return this.s[this.s.length - 1]; }
  getMin() { return this.mins[this.mins.length - 1]; }
}

All four operations are O(1). The auxiliary stack costs O(n) extra space but eliminates the O(n) scan you'd need without it.

Common bugs and edge cases

  • Pop on empty stack. Either return null/throw — but be consistent. Mixing the two styles in the same codebase is the source of "undefined is not a function" runtime errors.
  • Peek on empty stack. Same — pick a behavior and stick with it.
  • Stack overflow from recursion. When converting recursion to iteration with an explicit stack, the explicit stack lives on the heap and can hold millions of frames; the call stack typically holds tens of thousands. For deep recursion, the explicit-stack version is what survives.
  • Forgetting to handle the empty case at the end. In valid-parentheses, the stack must be EMPTY at the end — non-empty means there's an unmatched opener. Easy to miss.
  • Using a stack when you needed a queue. If your "process work items" loop produces results in the reverse order you expected, you grabbed a stack when you wanted a queue. Classic BFS-vs-DFS bug.

Frequently asked questions

What's a stack overflow?

A program runs out of stack space, usually because of unbounded recursion. Each function call pushes a frame onto the call stack (local variables, return address, saved registers). The OS allocates a finite stack — typically 1 MB on Linux, 512 KB on macOS for the main thread — and overflowing it crashes the program. The fix is either to add a base case to terminate recursion, or to convert the recursion to iteration with an explicit stack on the heap.

Why is push and pop O(1)?

The stack only ever touches one end (the top). Push appends to the end of the underlying array (or prepends to a linked list); both are constant-time. There's no shifting like inserting at the head of an array, no traversal like inserting in the middle of a linked list. One memory write, done.

Should I implement a stack with an array or a linked list?

Array-backed in almost every case. Modern dynamic arrays amortize push to O(1), have excellent cache locality, and use less memory per element (no per-node pointer overhead). Linked-list stacks win only when you need predictable per-operation latency (no occasional O(n) resize spike) or when memory is fragmented and you can't allocate a contiguous block.

How is recursion related to stacks?

Every recursive call pushes a stack frame onto the call stack. So any recursive algorithm can be rewritten iteratively using an explicit stack, and vice versa — they're computationally equivalent. Iterative-with-explicit-stack is preferred when recursion would overflow or when you need to pause/resume the computation.

How does a stack solve the valid-parentheses problem?

Walk through the string. Push every opening bracket. On a closing bracket, pop the top and check it matches. If the stack is empty when you try to pop, or the top doesn't match, the string is invalid. At the end, the stack must be empty. O(n) time, O(n) space — the canonical stack interview question.

Why do compilers use stacks for expression evaluation?

Operators have precedence and associativity rules. A compiler scans the expression and uses two stacks (operator stack + operand stack) — push operands, push operators, but before pushing a new operator, pop and apply any operator on the stack with higher or equal precedence. This is the shunting-yard algorithm, and it converts infix to postfix in O(n) without recursion.

What's the difference between a stack and the call stack?

A stack (data structure) is the abstract LIFO concept. The call stack is the OS-managed memory region your program uses to track active function calls. The call stack uses the stack abstraction internally — push frame on call, pop frame on return — but it lives in a specific reserved region of process memory, grows toward the heap, and is what overflows when recursion runs away.