Implementing a Stack with an Array
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.
Two Ways to Build the Same Stack
Suppose you are told that a stack supports push and pop, and that you may build it in two different ways. One way uses a linked list. The other uses an array. Before you choose, it helps to ask a practical question: 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?
What do you think happens?
In an array-based stack, which push is the costly one?
Reveal answer
A push that finds the array already full
Most pushes simply place a new item into the next free slot, which is Theta(1). When the array is full, there is no free slot, so the implementation must create a new, larger array and copy all n existing items into it before the new item can be placed. That push is Theta(n) in the worst case.
This article answers that question for both implementations. The answer is not the same for each, and the difference is more subtle than a simple fast-versus-slow comparison. One implementation keeps every push and pop equally cheap no matter what. The other is cheap almost all of the time but has one situation where a single push must do a great deal of work.
Measuring a Stack Operation by Its Size
When we analyze how long a stack operation takes, we need a way to measure the size of the problem. For stacks 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.
Run time is described by how it grows with n. Theta(1) means the run time is bounded by some fixed constant that does not depend on n. Theta(n) means the work grows in proportion to the number of items. Keeping n in view is what lets us compare the two implementations fairly, because both are judged by the same measure.
Why Linked List Operations Stay Constant
For a linked list implementation, the worst-case run time of both push and pop is Theta(1). Whether the stack has three items or three million, a single push or pop finishes within the same fixed amount of work.
The reason is visible in the structure of the operations. Each one is carried out by a handful of simple assignments that update the links at the top of the stack. Nothing in those steps requires visiting the other items. The total number of items on the stack never enters the calculation, so it cannot make the operation slower. A push links a new node to the front, and a pop unlinks the front node. In both cases the rest of the stack stays exactly where it was.
Tracing the Top Index in an Array
An array has a fixed amount of storage, and the implementation must keep track of where the top of the stack currently sits. Consider an array with four slots, numbered 0 through 3. Pushing A, B, and C fills slots 0, 1, and 2 in order, and the top is now at slot 2, the most recently written item. Slot 3 is still free.
A pop reverses the movement. Removing C reads slot 2 and moves the top back to slot 1, which now holds B. Each operation touches only the slot at the top. A push writes into the next free slot, and a pop reads the item at the top. Neither needs to move the other items, which is why an ordinary push on an array is Theta(1).
The Full-Array Push
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.
Pushing E onto a full four-slot array
The array holds A, B, C, D in slots 0 to 3, and the array is full. Push E.
- 1Detect the full array
There is no free slot, so E cannot be written directly into the current array.
- 2Create a larger array
A new array with more room is created.
- 3Copy the existing items
All n = 4 items, A, B, C, and D, are copied into the new array. This loop touches each of the n items.
- 4Place the new item
E is written into the next free slot of the new array, and the top moves to it.
This single push costs Theta(n) in the worst case, with n = 4 here. The copying cost grows with the number of items already on the stack.
Spreading the Copy Cost Across Pushes
The worst case of a single push is Theta(n), but the source notes that this 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). It is important not to read this as saying the expensive copy never happens. It does happen. The point is that it happens infrequently enough that, when its cost is spread across the many cheap pushes around it, the average cost per push stays bounded by a constant.
In this run, the expensive push is one event among several cheap ones. The cost of the copy is shared across the pushes that surround it, so the average cost per push stays constant. The source presents this as a natural sense of average behavior rather than a formal calculation, so the same level of detail applies here.
Mistakes Beginners Make with Stack Costs
Common mistake Saying an array push is always Theta(1) because most pushes are
Why it failsA push that finds the array full must copy all n items, which is Theta(n) in the worst case.
Do this insteadState the worst case and the average case separately. Ordinary pushes are Theta(1), and a full-array push is Theta(n).
Common mistake Treating the average case as proof that the copy never happens
Why it failsThe average-case claim says the copy is rare and its cost is spread across many cheap pushes. The copy still occurs.
Do this insteadDescribe the expensive copy as occurring infrequently, then explain how its cost is shared across the surrounding pushes.
Common mistake Assuming a linked list push depends on how many items are already stacked
Why it failsA linked list push only updates the links at the top, and it never visits the other items.
Do this insteadRecognize that the number of items on the stack never enters the calculation for linked list push or pop.
Common mistake Measuring stack run time by something other than the number of items, instead of n
Comparing the Two Implementations Side by Side
| Operation and measure | Linked list implementation | Array implementation |
|---|---|---|
| Push, worst case | Theta(1) | Theta(n), when the array is full and must be copied |
| Pop, worst case | Theta(1) | Theta(1) |
| Push, average case | Theta(1) | Theta(1) |
| What drives the cost | A few link assignments at the top, independent of stack size | Ordinary pushes write one free slot; a full array forces copying all n items |
Worst-case and average-case run times for stack operations under each implementation.
Neither implementation is simply better in every respect. 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. Each trades a different kind of predictability for the other.
Practice Tracing a Full Stack
An array-based stack has four slots and currently holds W, X, Y, and Z, so it is full. A push of V is requested. Identify which step is Theta(n) and state how many items must be copied. Then say what the worst-case and average-case run times are for this push in the array implementation.
Hints
- Ask whether the array has a free slot before the push writes anything.
- Count the items that already exist on the stack, since the copy touches each of them.
- Remember that the average case is about the cost spread over many pushes, not this single one.
Key Takeaways
- The size of a stack problem is measured by n, the number of items on the stack, so run time is judged by how it grows with n.
- A linked list stack has Theta(1) worst-case run time for push and pop because only a few link assignments at the top are needed.
- An array stack's ordinary push is Theta(1), but a push onto a full array creates a larger array and copies all n items, making it Theta(n) in the worst case.
- Because the full-array push is rare, the average-case run time of push on an array remains Theta(1), and worst-case and average-case claims should be stated separately.