patternDSA/Stacks
Core Pattern → Pattern 13

Stacks

Last In, First Out. Push when you encounter something, pop when you need to resolve it. The most recent thing is always at the top — that's the power of LIFO.

Frequency:★★★★☆
·~30 min·Beginner–Intermediate·6 problems
The Core Shock

Find matching brackets with a scan — O(n²). Stack resolves them as they appear — O(n)

Backward Scan
O(n²)
For each closer, scan back to find its opener
Stack Matching
O(n)
Push openers; pop and match on each closer

The insight: LIFO matches the structure of nested problems. The most recently opened bracket must be closed first. A stack naturally holds "the most recent unresolved thing" — push when you open something, pop when you close it.

Two Stack Uses

Every stack problem is one of these two uses.

Matching / Undo / History

Push items when encountered, pop to resolve or undo. Top of stack = most recent unresolved item.

Test: Ask: 'Does resolution depend on most recent item?' → Matching stack.
Balanced ParenthesesSimplify PathEvaluate ExpressionUndo OperationsDecode String
Monotonic Stack (Next Greater)

Maintain stack in increasing or decreasing order. New element breaks the order → pop and resolve smaller elements. Each element pushed/popped at most once → O(n).

Test: Ask: 'What is the next/previous greater/smaller element?' → Monotonic stack.
Next Greater ElementPrevious SmallerDaily TemperaturesLargest RectangleTrapping Rain Water

The 3-Step Framework

Three questions before every stack problem.

01
What gets pushed onto the stack?

For matching: push the opening element. For monotonic: push indices (not values) so you can fill result[idx] when popping. For path: push directory names.

stack.append(ch) # matching: push opener stack.append(i) # monotonic: push index stack.append(part) # path: push dir name
Push indices when you need to know WHERE something was — to fill a result array at that position.
02
When do you pop? What's the pop condition?

For matching: pop when closer matches top. For monotonic NGE: pop while stack[-1] < current. For path: pop on '..'.

if stack and stack[-1] == matching[ch]: stack.pop() while stack and nums[stack[-1]] < nums[i]: result[stack.pop()] = nums[i] if part == '..' and stack: stack.pop()
The pop condition defines 'resolve'. For NGE: current IS the NGE for everything smaller. For matching: current closer matches most recent opener.
03
What's the answer after the loop?

Matching: len(stack)==0. Monotonic: remaining = no NGE, stay -1. Path: join stack. Most answers are built during pops, not after.

return len(stack) == 0 # matching return result # NGE (filled during loop) return '/' + '/'.join(stack) # path
Don't forget the final check for matching problems — leftover openers mean the string is unbalanced even if no mismatch was found during the loop.

Pattern Recognition

Spot the stack before reaching for a loop.

Use a Stack When
"Most recent / last opened" must be resolved first
Nested structure: brackets, tags, functions
Undo/redo or back-navigation history
"Simplify" a path or expression
"Next/previous greater/smaller element"
Iterative DFS (replace call stack)
"Sort a stack" or simulate recursion
Evaluate postfix/prefix expressions
Common Stack Pitfalls
Pop without checking if stack is empty → IndexError
Peek with stack[-1] on empty stack → IndexError
NGE: pushing values not indices → can't fill result array
Monotonic: wrong direction (increasing vs decreasing)
Balanced: forgetting final 'return len(stack)==0' check
Path: forgetting to handle leading/trailing slashes
Sorting a stack: need a temp stack, not just .sort()
Binary conversion: digits appear reversed without reversing