Last-in-first-out in O(1): balanced parentheses, expression evaluation, min-stack design, and the gateway to monotonic-stack patterns.
0 of 8 lessons checked off
Introduction
What it is
A stack is a collection with one rule: you may only add, read, or remove at the top. Last In, First Out (LIFO).
It needs just five operations — push, pop, peek, is_empty, size — and every one of them is O(1).
Why it matters
Stacks model 'most recent unfinished thing': the newest open bracket must close first, the latest function call must return first, the last edit is undone first.
In interviews, an explicit stack is how you convert recursion to iteration, evaluate expressions, and (as a monotonic stack) crush a whole family of 'next greater element' problems from O(n²) to O(n).
How it works
Back it with a dynamic array (Python list): append pushes, pop() pops, [-1] peeks — the array end is the top.
A linked-list backing pushes/pops at the head instead; same O(1) costs, steadier worst-case latency, more memory per element.
Where it's used
The call stack that runs your programs, undo/redo in editors, browser back/forward, and the parser that validated this very page's HTML.
In interviews
Valid parentheses, evaluate reverse Polish notation, min stack, daily temperatures (monotonic), simplify path, decode string, largest rectangle in histogram.
Analogy: A stack is a pile of dinner plates: new plates go on top, and you take from the top. Getting the bottom plate means removing every plate above it — so nobody ever asks for the bottom plate.
Interactive diagram
Openers push; each closer must match the top. Scanning "{[()]}" shows why LIFO is exactly the right shape.
top
empty
Scan the string
Check whether "{[()]}" is balanced. Openers are pushed; each closer must match the most recent unmatched opener — exactly LIFO order.
1 / 8
Lessons in this topic
Check off lessons as you go — your progress is saved in this browser.
Stack fundamentals and LIFO
The contract, the five operations, and why each is O(1).
15 min
Array-based stack
Python list as a stack; amortised append; overflow in fixed buffers.
15 min
Linked-list-based stack
Head push/pop; trade-offs vs the array backing.
15 min
Balanced parentheses
The canonical stack problem: openers wait, closers must match the top.
20 min
Expression evaluation & infix/postfix/prefix
Why postfix needs no parentheses; evaluating RPN with one stack.
30 min
Min stack (design)
O(1) get-min by stacking (value, min-so-far) pairs.
20 min
Next greater element (preview)
The monotonic stack — full pattern page later in the roadmap.
15 min
Stack using queues
The classic conversion exercise and its cost asymmetry.
15 min
Operations
Push, pop, peek, is_empty, size
All five operations touch only the top element — no traversal ever happens, hence O(1) each.
top
empty
An empty stack
A stack exposes one end — the top. All three core operations (push, pop, peek) touch only that end, which is why each is O(1).
1 / 7
1classStack:2"""Array-backed stack. All operations O(1) (append amortised)."""34def__init__(self)->None:5self._items:list[int]=[]67defpush(self,value:int)->None:8self._items.append(value)# top = end of the list910defpop(self)->int:11ifnotself._items:12raiseIndexError("pop from empty stack")13returnself._items.pop()1415defpeek(self)->int:16ifnotself._items:17raiseIndexError("peek at empty stack")18returnself._items[-1]1920defis_empty(self)->bool:21returnnotself._items2223def__len__(self)->int:24returnlen(self._items)
Time: O(1) for every operation (push amortised)Space: O(n) total
Edge cases
pop/peek on an empty stack must raise, not return None silently.
A fixed-capacity backing adds an overflow case — check before push.
len on an empty stack is 0, not an error.
Common mistakes
Using list.pop(0) or insert(0, x) — that's the WRONG end and costs O(n).
Returning None on empty pop, which hides bugs until much later.
Balanced parentheses
Push every opener. On a closer, the stack top must be its partner; anything else — or leftovers at the end — means unbalanced.
top
empty
Scan the string
Check whether "{[()]}" is balanced. Openers are pushed; each closer must match the most recent unmatched opener — exactly LIFO order.
1 / 8
1defis_balanced(s:str)->bool:2"""Validate (), [], {} nesting. O(n) time, O(n) space."""3pairs={")":"(","]":"[","}":"{"}4stack:list[str]=[]5forchins:6ifchinpairs:# a closer7ifnotstackorstack[-1]!=pairs[ch]:8returnFalse9stack.pop()10elifchin"([{":# an opener11stack.append(ch)12returnnotstack# leftovers = unbalanced
Time: O(n)Space: O(n) worst case — all openers
Edge cases
A closer with an empty stack — ')(' fails on the first character.
Leftover openers — '((' must return False via the final emptiness check.
Empty string is balanced.
Common mistakes
Only counting opens minus closes — counts pass '([)]' which is invalid.
Forgetting the final `not stack` check, accepting '((('.
Evaluate postfix (Reverse Polish) notation
Operands push; an operator pops two, computes, and pushes the result. One pass, no precedence rules needed.
Evaluate postfix (Reverse Polish) notation
1defeval_rpn(tokens:list[str])->int:2"""Evaluate e.g. ["2","1","+","3","*"] → 9. O(n) time/space."""3stack:list[int]=[]4ops={5"+":lambdaa,b:a+b,6"-":lambdaa,b:a-b,7"*":lambdaa,b:a*b,8"/":lambdaa,b:int(a/b),# truncate toward zero9}10fortokenintokens:11iftokeninops:12b=stack.pop()# top is the RIGHT operand13a=stack.pop()14stack.append(ops[token](a,b))15else:16stack.append(int(token))17returnstack[0]
Time: O(n)Space: O(n)
Edge cases
Operand order: the first pop is the right-hand operand — subtraction and division break if swapped.
Division truncates toward zero: int(a / b), not a // b, for negative operands.
Single-token input like ['42'] returns 42.
Common mistakes
Popping a then b in that order and computing a − b (backwards).
Using // for division: −7 // 2 = −4 but the expected truncation is −3.
Min stack (O(1) minimum)
Store, alongside each value, the minimum of everything at or below it. Popping automatically 'rolls back' the minimum.
Min stack (O(1) minimum)
1classMinStack:2"""push/pop/top/get_minallO(1)bypairingeachvalue3withtheminimumofthestackuptothatpoint."""45def__init__(self)->None:6self._items:list[tuple[int,int]]=[]# (value, min_so_far)78defpush(self,value:int)->None:9current_min=min(value,self._items[-1][1])ifself._itemselsevalue10self._items.append((value,current_min))1112defpop(self)->int:13ifnotself._items:14raiseIndexError("pop from empty stack")15returnself._items.pop()[0]1617deftop(self)->int:18returnself._items[-1][0]1920defget_min(self)->int:21returnself._items[-1][1]
Time: O(1) for all four operationsSpace: O(n) — one extra int per element
Edge cases
get_min on empty should raise like pop.
Duplicated minimums pop correctly because each entry carries its own min.
Pushing a new global minimum: min pairs update naturally.
Common mistakes
Keeping a single min variable — unrecoverable after popping the minimum.
Scanning the stack in get_min (O(n)) — the design goal was O(1).
Complexity analysis
Operation
Best
Average
Worst
Space
push
O(1)
O(1)
O(n) on resize
—
pop
O(1)
O(1)
O(1)
—
peek / is_empty / size
O(1)
O(1)
O(1)
—
search for a value
O(1)
O(n)
O(n)
O(1)
balanced-parentheses scan
O(n)
O(n)
O(n)
O(n)
Searching a stack means popping through it — if you need search, a stack is the wrong structure.
Python implementation
Production-quality code with type hints, validation, and docstrings.
Linked-list-backed stack (steady O(1), no resizes)
LIFO contract and the five O(1) operations without hesitation.
The stack ↔ recursion equivalence: any recursion can be made iterative with an explicit stack.
Postfix evaluation and infix→postfix exist because stacks encode operator precedence.
The min-stack pairing trick — the template for 'augment a structure with O(1) aggregates'.
Classic follow-ups
"Implement a queue using two stacks" — amortised O(1) via an in-box and a flipped out-box.
"Max stack?" — same pairing trick with max; supporting popMax efficiently needs a fancier design (say so).
"What breaks with concurrent pushes?" — the read-modify-write on top needs locking; a fine systems tangent to acknowledge.
How to explain a stack solution
Name what the stack holds and its invariant: 'the stack holds unmatched openers, newest on top.' If you can't state the invariant, the stack is probably the wrong tool.
On monotonic problems, say what popping means ('this index found its answer') — that sentence is the whole algorithm.
Common mistakes
Wrong end of the list
insert(0, x) / pop(0) turn O(1) stack ops into O(n). The top must be the array END: append / pop().
No empty-stack guard
pop and peek on an empty stack should raise immediately. Silent None returns surface as confusing crashes two functions later.
Counting instead of stacking
Balance checking with a counter accepts interleaved '([)]'. Matching kinds requires remembering WHICH opener is unfinished — that's the stack.
RPN operand order flipped
The first pop is the right operand: for '6 2 /' compute 6 / 2, not 2 / 6.
Forgetting leftovers
A scan that ends with a non-empty stack means unclosed openers — return False, not True by default.
Practice problems
Hand-picked from the 355-problem DSA Atlas. Reveal hints and solutions progressively; mark and bookmark as you solve.
6 questions across concepts, code output, complexity, and scenarios. Answer all, then submit for explanations.
Frequently asked questions
Should I use Python's list or deque for a stack?
list is idiomatic and fast: append/pop at the end are amortised O(1). deque also works (append/pop on the right) with steadier worst-case latency. Never use the FRONT of a list.
What's the difference between infix, prefix, and postfix?
Where the operator sits: a+b (infix), +ab (prefix/Polish), ab+ (postfix/RPN). Postfix needs no parentheses or precedence rules, which is why compilers and calculators convert to it — using a stack.
When is a stack the wrong choice?
Whenever you need FIFO order (use a queue), random access (array), or lookup by key (hash map). If you find yourself digging below the top, the structure is telling you something.
Summary & cheat sheet
Key takeaways
Stack = LIFO with five O(1) operations; the array end is the top.
The invariant sentence — 'the stack holds X, newest on top' — designs the algorithm for you.
Balanced brackets, RPN evaluation, and undo are pure stack shapes.
Min stack: pair each value with the min-so-far for O(1) aggregates.
Recursion ↔ stack: same idea, different bookkeeper.
Formulas & cheat sheet
Push then pop sequences are balanced-bracket strings (Catalan structure)
Valid pop-sequence check: simulate with a stack in O(n)
Infix → postfix: operators wait on the stack until higher precedence arrives
Interview checklist
I can implement a stack both array-backed and linked.
I can write the bracket validator with all three failure modes.
I can evaluate RPN with correct operand order and truncation.
I can explain the min-stack pairing in one sentence.