Stack Basics
One-liner: a stack is a pile where you only touch the top, so it is the right tool whenever the most recent unfinished thing must be handled first — nested brackets, evaluating an expression, undo, or remembering a running minimum.
The analogy
A stack of plates in a canteen. You can only add a plate to the top and only take a plate from the top. If you put plate A, then B, then C, you must remove C, then B, then A. That "last in, first out" order is exactly how brackets close (the last one opened is the first one closed) and how a calculator waits for numbers before applying an operator.
Recognition signals
- Matching pairs or nesting: brackets, tags, "undo the last thing".
- Evaluating an expression where operands must wait until their operator arrives.
- A structure that must answer "what is the minimum / maximum so far?" even after removing elements.
- A recursion you want to write without recursion (the stack replaces the call stack).
- Items that interact only with the one just before them, such as cars that cannot overtake.
Step-by-step walkthrough
Evaluate ["2", "1", "+", "3", "*"] (that is (2 + 1) × 3):
2→ push. Stack:[2].1→ push. Stack:[2, 1].+→ pop1, pop2, push2 + 1 = 3. Stack:[3].3→ push. Stack:[3, 3].*→ pop3, pop3, push9. Stack:[9]. The answer is9.
Code template
In Go a stack is a slice. Push is append, pop trims the end:
// Stack is a slice used as a pile of plates: push and pop only touch the end.
type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Len() int { return len(s.items) }
func (s *Stack[T]) Peek() T { return s.items[len(s.items)-1] }
func (s *Stack[T]) Pop() T {
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v
}s.items[len(s.items)-1]The top of the stack is the end of the slice. Appending and trimming the end are O(1); touching the front would be O(n).
Stack[T any]Generics let one type serve ints, bytes or anything else. For a quick solution you can also use a bare []int and write the two lines inline.
The real solutions
Valid Parentheses is on the Monotonic Stack page: push each opener, and each closer must match the top.
Min Stack — each element remembers the minimum at the time it was pushed:
// MinStack (LeetCode 155): every element remembers the minimum at the time it was pushed,
// so after a pop the previous minimum is simply the new top.
type MinStack struct{ items []struct{ val, min int } }
func (m *MinStack) Push(v int) {
lo := v
if n := len(m.items); n > 0 && m.items[n-1].min < lo {
lo = m.items[n-1].min
}
m.items = append(m.items, struct{ val, min int }{v, lo})
}
func (m *MinStack) Pop() { m.items = m.items[:len(m.items)-1] }
func (m *MinStack) Top() int { return m.items[len(m.items)-1].val }
func (m *MinStack) GetMin() int { return m.items[len(m.items)-1].min }Evaluate Reverse Polish Notation — numbers wait; an operator takes the two most recent. Note the pop order for - and /:
// EvalRPN (LeetCode 150): "3 4 +" puts the operator AFTER its operands, so operands wait on a stack.
func EvalRPN(tokens []string) int {
var st Stack[int]
for _, t := range tokens {
switch t {
case "+", "-", "*", "/":
b, a := st.Pop(), st.Pop() // the FIRST pop is the right-hand operand
switch t {
case "+":
st.Push(a + b)
case "-":
st.Push(a - b)
case "*":
st.Push(a * b)
default:
st.Push(a / b) // Go truncates toward zero, as the problem requires
}
default:
n, _ := strconv.Atoi(t)
st.Push(n)
}
}
return st.Pop()
}Generate Parentheses is backtracking with two rules, and the rules are the whole problem:
// GeneratePar (LeetCode 22): build the string one character at a time.
// Rule 1: you may add "(" while some are left. Rule 2: you may add ")" only if it closes an open one.
func GeneratePar(n int) []string {
var out []string
var build func(cur []byte, open, close int)
build = func(cur []byte, open, close int) {
if len(cur) == 2*n {
out = append(out, string(cur))
return
}
if open < n {
build(append(cur, '('), open+1, close)
}
if close < open {
build(append(cur, ')'), open, close+1)
}
}
build(nil, 0, 0)
return out
}Car Fleet — once cars are ordered by position, a stack of fleets (or a single "slowest so far" number) is enough:
// CarFleet (LeetCode 853): sort by position, nearest the target first, and compute each car's arrival time.
// A car behind can never pass, so if it would arrive sooner than the fleet ahead it catches up and joins it.
// Only a car that arrives LATER than every fleet ahead leads a new fleet.
func CarFleet(target int, position, speed []int) int {
idx := make([]int, len(position))
for i := range idx {
idx[i] = i
}
sort.Slice(idx, func(a, b int) bool { return position[idx[a]] > position[idx[b]] })
fleets := 0
slowest := 0.0
for _, i := range idx {
t := float64(target-position[i]) / float64(speed[i])
if t > slowest { // arrives after everything ahead: cannot catch up, starts a new fleet
fleets++
slowest = t
}
}
return fleets
}Complexity
| Approach | Time | Space |
|---|---|---|
| Push / pop / peek on a slice | O(1) | O(n) |
| Min Stack, all operations | O(1) | O(n) |
| Evaluate RPN | O(n) | O(n) |
| Generate Parentheses | O(4ⁿ/√n) — the Catalan number of results | O(n) depth |
Common mistakes
Practice ladder
- 1.Valid ParenthesesEasyWhat must the closer match?
- 2.Medium
- 3.Medium
- 4.Generate ParenthesesMediumWhen is adding ")" legal?
- 5.Car FleetMediumSort first. Compare arrival times.
- 6.Medium
- 7.Hard
Which pattern? Drills
A text editor must support typing a character, undoing the last action, and reporting the smallest character typed so far, all in constant time.
Which pattern?