How Stacks Work
Last in, first out (LIFO). Push, pop, and peek — what a stack is, why it matters, and where it appears in real systems.
What Is It?
A stack is a way to store items where you can only add or remove from the top. That's it. You can't reach into the middle.
It follows the LIFO rule: Last In, First Out. The last thing you add is the first thing you take out. Like a stack of plates.
A stack has three core operations:
- push(value) — Add an item to the top.
- pop() — Remove the item from the top and return it.
- peek() — Look at the top item without removing it.
Plus one helper:
- isEmpty() — Returns true if the stack has nothing in it.
You can only touch the top. You can't access the middle or bottom directly. That's the whole point.
push(40)|v+------+| 40 | <-- top (most recently added)+------+| 30 |+------+| 20 |+------+| 10 | <-- bottom (first added, last to come out)+------+
Call pop() and you get 40. Call it again and you get 30. Then 20. Then 10.
Analogy
Picture a stack of heavy dinner plates in a restaurant kitchen. The staff adds clean plates one at a time to the top. When a waiter needs a plate, they grab the one on top — they can't pull from the middle without the whole pile falling.
The last plate placed is always the first one grabbed. That's LIFO.
Now imagine each plate has a number:
- Stack plate 10, then 20, then 30.
- The waiter grabs 30 first (it's on top).
- Next: 20.
- Last: 10.
You remove them in the exact opposite order you added them.
Try It Yourself
How It Works
The LIFO Principle
LIFO means you can only add and remove from the top. Nothing below the top is reachable until everything above it is gone.
This isn't a limitation — it's the feature. Lots of real problems naturally need things processed in reverse order. Stacks give you exactly that.
The Three Core Operations
push(value)
Adds an item on top. The new item becomes the top.
Before push(30)
- 20 ← top
- 10
Stack has two elements
top = 20
After push(30)
- 30 ← top
- 20
- 10
30 is added on top
top = 30
pop()
Removes the top item and returns it. If the stack is empty, that's an error called underflow — you tried to take from an empty stack.
Before pop()
- 30 ← top
- 20
- 10
Stack has three elements
top = 30
After pop() → returns 30
- 20 ← top
- 10
30 is removed from top
top = 20
peek()
Returns the top item without touching the stack. Nothing changes.
peek() on: Returns: 30 (stack unchanged)+------+ +------+| 30 | <-- top | 30 | <-- top+------+ +------+| 20 | | 20 |+------+ +------+
isEmpty()
Returns true if there's nothing in the stack. Always check this before calling pop() or peek() — otherwise you get an underflow error.
Where Stacks Show Up in Real Life
1. The Undo Button
Every action you do in a text editor gets pushed onto a stack. When you press Ctrl+Z, the most recent action is popped off and reversed. That's why undo goes backward — LIFO.
Actions stack:+------------------+| delete word | <-- top (most recent)+------------------+| type "hello" |+------------------+| paste text |+------------------+Undo pops: "delete word" -> reverses it (word reappears)
2. Browser Back Button
Visit pages A then B then C. Each page is pushed onto a stack. Hit "back" and C is popped — you see B. Hit back again and B is popped — you see A.
Visit stack:+-------+| C | <-- current page+-------+| B |+-------+| A |+-------+Back button: pop C, now B is on top
3. The Function Call Stack
When your program calls a function, that function gets pushed onto a call stack. When it finishes, it gets popped and your program picks up where it left off. This is how every programming language handles function calls.
If you call too many functions without returning (like an infinite loop of function calls), the call stack fills up and you get a stack overflow error. That's literally what a stack overflow is.
function main() calls a()function a() calls b()function b() is running nowCall stack:+--------+| b() | <-- running right now+--------+| a() | <-- waiting for b()+--------+| main | <-- waiting for a()+--------+
4. Bracket Matching
When a code editor checks that your ({[]}) brackets are properly nested, it uses a stack. We'll build this in the Stack Applications lesson.
Tracing Stack Operations
Let's trace through a mix of pushes and pops:
Notice the pops don't always come out in the same order as the pushes. The LIFO rule only applies to whatever is currently in the stack at each moment.
Examples
Example 1: Reversing a Sequence
Push 1, 2, 3, 4, 5 onto a stack, then pop all of them. You get 5, 4, 3, 2, 1. Stacks naturally reverse things.
Example 2: Checking Balanced Brackets
For ({[]}), push each opening bracket. When you see a closing bracket, pop and check if it matches. If the stack is empty at the end and every match was correct, the brackets are balanced.
Example 3: Depth-First Search
When exploring a maze or a graph, you go as deep as possible before backtracking. A stack tracks where you came from — you always backtrack to the most recent unfinished spot first.
Common Mistakes
1. Popping from an empty stack. Always call isEmpty() before pop() or peek(). Popping an empty stack crashes your program.
2. Mixing up LIFO and FIFO. Stacks are LIFO — last in, first out. Queues are FIFO — First In, First Out, meaning the first thing added is the first thing removed. If you need first-come-first-served, you want a queue, not a stack.
3. Trying to access the middle. Stacks don't support that. You can't get the third element without popping the two above it. If you need to do that, use an array instead.
4. Forgetting that peek doesn't remove. peek() just looks. If you want to look AND remove, use pop().
5. Not spotting stack problems. If a problem involves reversal, matching/nesting, backtracking, or undo — it's probably a stack problem.
Best Practices
Always check isEmpty before pop or peek. This one habit prevents most stack-related bugs.
Use stacks when you need reversal or nesting. Bracket matching, undo systems, depth-first search — all natural fits.
Remember the call stack. Every time you write a recursive function, you're using the call stack. Understanding this helps you debug and reason about recursion.
Keep the interface simple. A stack should only expose push, pop, peek, isEmpty, and size. Don't add random-access methods — that defeats the purpose.