Concepts / Implementing a Stack with an Array

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.

  • Programming

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?

  • The first push onto an empty stack

  • A push onto a stack whose array has a free slot

  • A push that finds the array already full

  • Every push costs the same amount

Reveal answer
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.

slot 0Aslot 1Bslot 2C, topslot 3free
Where is the top of the stack after these pushes, and which slot did the last push write?

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.

Aslot 0AcopiedBslot 1BcopiedCslot 2CcopiedDslot 3, fullDcopiedEnew push
When a push finds the array full, how many stored items must be copied to make room for the new one?
Worked example

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.

  1. 1
    Detect the full array

    There is no free slot, so E cannot be written directly into the current array.

  2. 2
    Create a larger array

    A new array with more room is created.

  3. 3
    Copy 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.

  4. 4
    Place the new item

    E is written into the next free slot of the new array, and the top moves to it.

Result

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.

push 1cheappush 2cheappush 3cheappush 4cheap, array now fullpush 5copy 4 items, then placepush 6cheap
Across a run of pushes, how do the occasional expensive copies spread out among the cheap pushes?

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 fails

    A push that finds the array full must copy all n items, which is Theta(n) in the worst case.

    Do this instead

    State 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 fails

    The average-case claim says the copy is rare and its cost is spread across many cheap pushes. The copy still occurs.

    Do this instead

    Describe 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 fails

    A linked list push only updates the links at the top, and it never visits the other items.

    Do this instead

    Recognize 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 measureLinked list implementationArray implementation
Push, worst caseTheta(1)Theta(n), when the array is full and must be copied
Pop, worst caseTheta(1)Theta(1)
Push, average caseTheta(1)Theta(1)
What drives the costA few link assignments at the top, independent of stack sizeOrdinary 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

MEDIUM

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.