Stack
A last-in, first-out collection where all insertions and removals happen at one end, the top.
Definition
A stack is a collection with two core operations: push adds an element to the top, pop removes the most recently pushed element. Because the last element in is the first one out, the order is called LIFO.
Stacks are usually backed by a Dynamic Array (push/pop at the end) or a Singly Linked List (push/pop at the head). Both give O(1) per operation; the array version is faster in practice due to cache locality and no per-node allocation.
The call stack of every program is a stack: each function call pushes a frame, each return pops it. This is why any recursive algorithm can be rewritten iteratively with an explicit stack.
Intuition
A mental model before the formal terms.
Picture a stack of plates in a cafeteria spring-loaded dispenser. You can only add a plate on top and only take the top plate. To reach the plate at the bottom you must remove everything above it first.
Another model: an undo history. Every action goes on top; "undo" pops the latest one. Nobody wants to undo the *first* thing they did while keeping the last.
How it works
- Keep an array
dataand treatdata[len - 1]as the top. push(x): appendxto the end ofdata.pop(): remove and return the last element; raise or return a sentinel if empty.peek(): return the last element without removing it.isEmpty()/size(): read the length.
Why it works
Appending to and removing from the end of a dynamic array never shifts other elements, so each operation is O(1) amortized. Resizes are rare (geometric growth) and cost O(n) spread over n pushes.
LIFO order exactly mirrors nested structure: when you open a bracket, the matching close bracket must come before any earlier bracket closes. Any problem with "most recent unfinished thing first" semantics maps to a stack.
Operations
| Operation | Description | Cost |
|---|---|---|
| push(x) | Add x on top of the stack. | O(1) amortized |
| pop() | Remove and return the top element. | O(1) |
| peek() / top() | Return the top element without removing it. | O(1) |
| isEmpty() | True when the stack holds no elements. | O(1) |
| size() | Number of elements currently stored. | O(1) |
Recognition
How to tell a problem wants this.
- The problem involves matching pairs or nesting: parentheses, HTML tags, file paths with
... - You need to undo or backtrack to the most recent state: browser history, text editor undo, DFS.
- An expression must be evaluated with operator precedence (infix → postfix, calculator problems).
- A recursive solution blows the call stack and you need an iterative version.
Interactive demo
Play, step, change the input. ← → and space work too.
1stack = []2for ch in s:3 if ch is an opener: stack.push(ch)4 else if stack empty or stack.top does not match ch: return invalid5 else: stack.pop()6return stack emptyPseudocode
1class Stack:2 data = []3 push(x): data.append(x)4 pop(): if empty: error; return data.removeLast()5 peek(): return data[len - 1]6 isEmpty(): return len == 0Implementation
1from typing import Generic, TypeVar2 3T = TypeVar("T")4 5 6class Stack(Generic[T]):71 · Backing array as the stack8 def __init__(self) -> None:9 self._data: list[T] = [] # _data[-1] is the top10 112 · Push on top12 def push(self, x: T) -> None:13 self._data.append(x)14 153 · Pop and peek with emptiness guards16 def pop(self) -> T:17 if not self._data:18 raise IndexError("pop from empty stack")19 return self._data.pop()20 21 def peek(self) -> T:22 if not self._data:23 raise IndexError("peek from empty stack")24 return self._data[-1]25 264 · Size and emptiness27 def is_empty(self) -> bool:28 return not self._data29 30 def __len__(self) -> int:31 return len(self._data)- A
listis the backing store;_data[-1]is the top. pushislist.append— amortized O(1) at the end.popguards emptiness and callslist.pop()with no index, which removes the last element in O(1).peekindexes[-1];is_emptyuses the truthiness of the list;__len__makeslen(stack)work.
- A plain
listalready is a stack —append/popare the idiom; this class exists to name the intent and add guards. collections.dequealso gives O(1)append/popand is the drop-in when the same object must serve as a queue too.list.pop(0)is the queue-shaped trap: it shifts every element, O(n).
- Using
list.pop(0)orinsert(0, x)and accidentally paying O(n) per operation. - Catching the wrong exception: an empty
list.pop()raisesIndexError, notValueError. - Copying the stack with
stack[:]in a loop — O(n) per copy dominates the algorithm.
- Built-ins: C++ has the
std::stackadapter; Pythonlistand JS/TSArrayare stacks natively; none of that changes the O(1) end-operations. - Empty pop behaviour: JS
Array.pop()silently returnsundefined; Python raisesIndexError; C++vector::pop_backon empty is undefined behaviour — the guard is mandatory, not cosmetic. - C++
std::stack::pop()returnsvoid(readtop()first); every other language returns the removed value. - Memory: a popped C++ vector keeps its capacity; JS and Python shrink internally at their own discretion — none guarantees immediate release.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | O(n) | O(n) | Only the top is O(1). |
| Search | O(n) | O(n) | |
| Insert | O(1) | O(n) | Worst case is an array resize; amortized O(1). |
| Delete | O(1) | O(1) | Only the top can be deleted. |
| Update | — | — | |
| Push | O(1) | O(n) | Amortized O(1). |
| Pop | O(1) | O(1) | |
| Peek | O(1) | O(1) | |
| Space | O(n) | ||
Advantages & disadvantages
- All core operations are
O(1); trivially simple to implement on top of an array. - Naturally models nesting, recursion, and reversal without extra bookkeeping.
- Memory is contiguous in the array-backed version, giving excellent cache behavior.
- No random access: reaching the k-th element from the top costs
O(k)pops. - Searching for a value is
O(n). - Array-backed stacks can waste capacity after many pops unless you shrink explicitly.
Use cases
- Function call management and recursion elimination (iterative Depth-First Search (DFS)).
- Syntax checking and expression evaluation in compilers and calculators.
- Undo/redo, browser back button, navigation history.
- Backtracking search states and Monotonic Stack problems (next greater element, histogram area).
- Nested or paired structure must be validated or evaluated.
- You need to revisit the most recently deferred item first (DFS, backtracking, undo).
- Converting recursion to iteration to avoid stack-overflow limits.
- Items must be processed in arrival order — use a Queue.
- You need access to both ends or the middle — use a Deque or Dynamic Array.
- You need the minimum/maximum element repeatedly — use a Priority Queue (or an auxiliary min-stack for min-only).
Alternatives
Common mistakes
- Popping without checking for emptiness —
[]. pop()in Python raises,Array.prototype.pop()in JS silently returnsundefined. - Using
list.pop(0)/Array.shift()as a stack pop — that isO(n)and makes it a queue, not a stack. - In Java, using the legacy
java.util.Stack(synchronized, extends Vector) instead ofArrayDeque. - Forgetting to handle leftover elements after the input is consumed (e.g. unmatched opening brackets remaining on the stack means invalid).
Interview patterns
- Valid parentheses: push opens, pop and compare on closes, stack must be empty at the end.
- Min stack: keep a second stack of running minimums so
getMin()isO(1). - Evaluate reverse Polish notation: push operands, pop two on each operator.
- Simplify a Unix path: push directory names, pop on
... - Iterative DFS / tree traversals using an explicit stack.
- Deciding whether O(n²) can be improvedIntermediate
- Stack versus queueBeginner
- Recursion versus iterationIntermediate
- Next greater element and the monotonic stackIntermediate
- Daily TemperaturesIntermediate