Implementing a Stack with a Linked List
The size of a stack problem is measured by the number of items on the stack, which lets us ask how run time grows as the stack grows.
Try it: Linked List
How a singly linked list stores values in separate nodes joined by next pointers, and how inserting or deleting only rewires pointers.
How it works
- HEAD points at the first node; each node holds a value and a pointer to the next node; the last node points to NULL.
- To insert, create a node, store its value, point it at its successor, then point its predecessor (or HEAD) at it.
- To delete, walk to the node and make its predecessor skip over it.
- Searching means following next pointers from HEAD until the value or NULL is reached.
Default run (9 steps): HEAD → 10 → 20 → 30 → NULL. 3 of 10 nodes. Choose an operation. Example: append 40. … Done: HEAD → 10 → 20 → 30 → 40 → NULL.
Loading the simulation…
Two Ways to Build the Same Stack
Imagine you are told that a stack supports push and pop, and that you may build it in one of two ways. One way uses a linked list. The other uses an array. Before choosing, a practical question is worth asking: when a single stack operation runs, how much work could it end up doing, and does that amount of work depend on how full the stack already is?
The two implementations answer this question differently. One keeps every push and pop equally cheap no matter what. The other is cheap almost all of the time but has one situation in which a single push must do a great deal of work. The difference is more subtle than a simple fast-versus-slow comparison, so it is worth working through carefully.
What do you think happens?
In an array-based stack, which push is the costly one?
Reveal answer
A push onto the stack when the array has no free slot left
Most pushes write into the next free slot in constant time. The expensive case is the push that finds the array full, because the implementation must create a larger array and copy every existing item into it before the new item can be placed.
Measuring a Stack Problem by Its Size
When we analyze how long a stack operation takes, we first need a way to measure the size of the problem. For a stack, the natural choice is the number of items currently on the stack. Call that number n. The question then becomes how the running time of push or pop grows as n grows.
The size of a stack problem is the number of items currently on the stack, written n. Run-time analysis asks how the time taken by push or pop changes as n changes.
This choice of size matters because a stack's operations only ever touch the top of the stack. Whether the answer depends on n is the central question for each implementation. Some implementations never let n affect the work; others let it affect the work in specific situations.
Tracing Push and Pop on a Linked List
In a linked list implementation, each stack item lives in a node, and each node holds a link to the node beneath it. The stack keeps track of the top through a head pointer. Pushing a new item means creating a node whose link points to the current top, then moving the head pointer to the new node. Popping means taking the node at the head, moving the head pointer down to the node it links to, and returning the removed item.
head -> B -> AStack holds B on top of A
head -> C -> B -> AStack holds C on top of B on top of A
Notice what did not change. The nodes B and A were not visited, copied, or rearranged. Only the head pointer and the link of the new node were updated. The same is true of pop, which only moves the head pointer down past the removed node. Because the operations consist of a handful of simple assignments at the top of the stack, nothing in them requires visiting the other items.
Counting the work for a push on a small and a large stack
Compare the work done by one push onto a stack of 3 items with one push onto a stack of 3,000,000 items, in a linked list implementation.
- 1Create the new node
The new node is created with the new item in it. This step does not depend on how many items are below it.
- 2Link the new node to the top
The new node's link is set to point to the current top node. Finding the top takes no searching, because the head pointer already refers to it.
- 3Move the head pointer
The head pointer is set to the new node. This is one assignment.
- 4Compare the two stacks
The same three steps run in both cases. The count of 3 or 3,000,000 never enters the calculation.
Both pushes finish within the same fixed amount of work, so the worst-case run time of push is Theta(1).
- push creates one node and makes two link assignments
- pop makes one assignment to move the head pointer down
Why Linked-List Operations Stay Constant
For a linked list implementation, the worst-case run time of both push and pop is Theta(1). In plain terms, the run time is bounded by some fixed constant that does not depend on how many items the stack holds. Whether the stack has three items or three million, a single push or pop finishes within the same fixed amount of work.
The total number of items on a linked-list stack never enters the calculation of push or pop, so it cannot make either operation slower.
This is the reason the linked list has predictable behavior. Every operation looks the same from the outside, regardless of history. The cost of the next push does not depend on the pushes that came before it.
Tracing a Push on a Full Array
An array has a fixed amount of storage. Most pushes simply place a new item into the next free slot, and for these the run time is Theta(1). The trouble comes when the array is full and there is no free slot left. At that moment the implementation cannot simply write one more item, so it must make room in a different way.
The method is to create a new, larger array and copy every existing stack item into it, after which the new item can be placed. Copying takes time in proportion to the number of items already on the stack. Because the copying loop touches each of the n items, a push that triggers it has a worst-case run time of Theta(n).
One expensive push among cheap pushes
A stack implemented with an array of capacity 4 already holds 4 items. A fifth item is pushed. What work does that push do compared with a push into a free slot?
- 1Detect the full array
No free slot remains, so the item cannot be written directly into the array.
- 2Create a larger array
A new, larger array is created to hold the items.
- 3Copy the existing items
All 4 existing items are copied into the new array, one at a time. This copying loop touches each of the n items.
- 4Place the new item
The fifth item is written into the next free slot of the new array.
- 5Compare with a cheap push
A push into a free slot performs a single write, which does not depend on n.
The push that finds the array full has worst-case run time Theta(n), while a push into a free slot is Theta(1).
Worst Case Versus Average Case for Arrays
The worst case tells us how bad one operation can be. The average case tells us what to expect over a sequence of operations. For the array implementation these two measures give different pictures. The worst case of a single push is Theta(n), but that worst case occurs only rarely. Because of that rarity, there is a natural sense in which the average run time of push on an array remains Theta(1).
The source presents this average-case reasoning as a natural sense of average behavior rather than as a formal calculation, so it should be understood at that level. The key habit is to state worst-case and average-case claims separately. A single push on an array can cost Theta(n), and the same array's pushes can still average Theta(1) across a long sequence.
Comparing the Two Implementations
Placing the two implementations next to each other makes the contrast clearer. The linked list keeps every push and pop at a fixed cost regardless of stack size. The array keeps ordinary pushes cheap but accepts one expensive case, the push that finds the array full. Neither implementation is simply better in every respect; each trades one kind of predictability for another.
| Operation | Linked list: worst case | Array: worst case | Array: average case |
|---|---|---|---|
| Push | Theta(1) | Theta(n) when the array is full | Theta(1) |
| Pop | Theta(1) | Not described as expensive in the source | Not described in the source |
Run times for the two stack implementations, as stated in the source
The linked list gives a uniform guarantee for every operation. The array gives cheap typical pushes with one occasional expensive push. Choosing between them means deciding which kind of predictability matters more for the task.
Mistakes When Analyzing Stack Costs
Common mistake Treating the size of a stack problem as something other than the item count
Why it failsThe natural measure of a stack's problem size is the number of items currently on it, called n. Analysis that ignores n cannot answer how running time grows.
Do this insteadDefine n as the number of items on the stack, then ask how the time of each operation changes as n changes.
Common mistake Assuming a linked-list push slows down as the stack grows
What it looks likePushing onto a stack of three million items seems like it should take longer than pushing onto a stack of three items.
Why it failsThe operation only creates a node and updates a few links at the top. The total item count never enters the calculation.
Do this insteadRecognize that the worst case of push and pop on a linked list is Theta(1).
Common mistake Saying an array push is always Theta(1) because most pushes are
What it looks likeA push onto an array that is already full.
Why it failsWhen the array has no free slot, a new larger array must be created and all n items copied, so that single push is Theta(n) in the worst case.
Do this insteadState the worst case as Theta(n) and the average case as Theta(1), and keep the two claims separate.
Common mistake Reading the average case as meaning the expensive copy never happens
Why it failsThe expensive copy does occur. The average stays bounded because the rare expensive push is spread across many cheap pushes.
Do this insteadDescribe the average case as the cost per push over a sequence of operations, not as the absence of expensive operations.
Practice Tracing Stack Costs
A linked-list stack holds 500,000 items, and an array-based stack of capacity 500,000 is completely full. For each stack, state the worst-case run time of one push, and explain in one sentence what work that push does.
Hints
- Ask whether the linked-list push depends on the number of items already on the stack.
- Ask whether the full array has a free slot to write into.
- For the array, the work includes creating a larger array and copying every existing item.
Key Takeaways
- The size of a stack problem is the number of items on the stack, called n, and run-time analysis asks how operation times grow as n grows.
- A linked-list stack has worst-case Theta(1) push and pop, because each operation is a few assignments at the top that do not depend on n.
- An array-based stack's push is usually Theta(1), but when the array is full a larger array must be created and all n items copied, making that push Theta(n) in the worst case.
- Because the full-array case is rare, the array's average-case push remains Theta(1); worst-case and average-case claims should always be stated separately.