The Last One In

You're writing an essay and realize you misspelled a word three paragraphs back. You hit undo. Which edit gets reversed? Not the first one you made. Not a random one. The most recent one — the last thing you typed. You hit undo again and the edit before that disappears. Again, and the one before that. Every undo peels back the most recent change, in reverse order, always.

Now think about your browser's back button. You visit a search page, click a result, follow a link inside that result. You press back. Which page appears? The one you just left — the link you followed. Not the search page, not your homepage. The last page you visited is the first one you return to.

Or picture a stack of dinner plates in a restaurant kitchen. You wash a plate and set it on top of the pile. Another plate, on top. Another. When the next order comes in, which plate does the cook grab? The one on top — the one you placed most recently. Nobody reaches into the middle of the stack. Nobody pulls from the bottom. The plate that went on last comes off first.

This pattern — the most recent thing is always the first to come back — shows up everywhere. Undo systems, browser history, plate stacks, function calls returning in reverse order. It feels intuitive when you encounter it, almost obvious. But there's a deeper question hiding inside it: how would you build a system that enforces this rule?

Suppose someone hands you a growing list of values and says, “Whenever I ask for one back, always give me the most recent one.” How do you guarantee that? You could timestamp every insertion and scan for the largest timestamp each time. You could maintain a sorted order. You could keep a pointer to the “most recent” element and update it manually. All of these work, but they're doing too much work. There has to be a simpler answer — one where the data structure itself does the bookkeeping for you.

So: how do you build a container that automatically hands back the most recent value, without scanning, sorting, or tracking pointers? Hold that question. The next screen lets you play with the answer before it has a name.

Building the Stack

Below is an empty container and a sequence of values waiting to be added. Push them on one at a time, then — before you remove anything — predict which value comes off first. Commit to your prediction; the reveal will tell you whether your mental model matches the rule the container is enforcing. Don't just click through. The whole point is the moment of friction when the answer surprises you (or doesn't).

FIG. 1 — THE STACK, BY HAND
00
— Push, predict, pop — feel the rule before you write it —

Every removal returned the most recently pushed value. Every “look without removing” showed the current top, with nothing else moved. You didn't search. You didn't compare. The constraint — only one end is reachable — did the work for you.

That rule has a name. The structure you just used is called a stack, and its discipline is LIFO — Last In, First Out. The three operations you exercised have names too:

  • push — add a value to the top
  • pop — remove and return the value on top
  • peek — look at the top value without removing it

That's the entire interface. No insertAt, no removeFrom, no findIndex. The restricted API is the point: by limiting access to a single end, you get LIFO behavior for free. The structure doesn't need to be clever. It just needs to be constrained.

Tracing Through Code

Now connect the physical intuition to actual code. Below is a sequence of push and pop calls on an initially empty stack. Before each pop, predict the return value — then advance to see the stack state change and the active line move. Notice how the array backing the stack mirrors exactly what you were manipulating visually in the previous screen.

FIG. 2 — CODE, LINE BY LINE
1
const stack: number[] = []
2
stack.push(4)
3
stack.push(1)
4
stack.push(8)
5
const a = stack.pop()   // → ?
6
stack.push(6)
7
const b = stack.pop()   // → ?
8
const c = stack.pop()   // → ?
9
// stack is now [4]
— Each variable on the left receives whatever pop returns — predict every one —

The mapping is direct. Each push() call appends to the top of the backing array. Each pop() removes from the top. The variable holding the popped value takes on exactly the value you predicted. There's no hidden reordering, no internal bookkeeping — the code does precisely what the physical stack did. Last in, first out, every time.

Under the Hood

A stack is typically backed by a dynamic array. push appends to the end. pop removes from the end. peek reads the last element. All three operations are O(1) — constant time — because they only ever touch the tail of the array. No element shifting, no scanning, no reallocation (amortized). One operation, one element, done.

But that “end of the array” detail is doing more work than it looks. What if you built the stack the other way — push inserts at the front using unshift, and pop removes from the front using shift? Logically, it still works. The most recent value is always at index 0, and removing from index 0 gives you LIFO order. The behavior is correct.

Before reading the next paragraph, commit to an answer:

Suppose arr holds 1000 elements. You call arr.unshift(x), inserting x at the front. How many array slots does the runtime have to touch (read or write) to make that happen?

The performance collapses exactly like that. Every unshift shifts the entire array one slot to the right to make room at position 0. Every shift shifts the entire array one slot to the left to close the gap. Both are O(n) operations. A stack that's supposed to be fast becomes a stack that gets slower as it grows — and you might not notice until your data set is large enough for the linear cost to hurt.

The implementation choice — always operate at the end of the array — is what makes a stack fast. It's not arbitrary. It's the reason arrays and stacks fit together so naturally: arrays are cheap at the tail and expensive at the head, and stacks only need the tail.

Fill in the blanks below to build a stack class. Each blank corresponds to a single array operation — push to the end, pop from the end, read the last element.

FIG. 3 — O(1) AT THE TAIL
1
class Stack<T> {
2
private items: T[] = [];
3
4
push(val: T): void {
5
this.items.___method___(val);
6
}
7
8
pop(): T | undefined {
9
return this.items.___popMethod___();
10
}
11
12
isEmpty(): boolean {
13
return ___emptyCheck___;
14
}
15
16
peek(): T | undefined {
17
return this.items[___peekIdx___];
18
}
19
}
— Four blanks. Three methods. One end of the array. —

Three methods, each one line of real logic, all O(1). That's the complete implementation. Push, pop, and peek each touch only the last element of the backing array, which is why stacks are so efficient despite being so simple. You'll see this tiny data structure powering bracket matching, expression evaluation, undo systems, and depth-first traversal in the lessons ahead. The constraint — access only at one end — is small, but it unlocks a surprising amount of algorithmic machinery.