Concepts / Implementing a Stack with a Linked List

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.

  • Programming
Interactive lab

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

  1. HEAD points at the first node; each node holds a value and a pointer to the next node; the last node points to NULL.
  2. To insert, create a node, store its value, point it at its successor, then point its predecessor (or HEAD) at it.
  3. To delete, walk to the node and make its predecessor skip over it.
  4. 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.

Educational simulation

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?

  • The first push onto an empty stack

  • A push onto the stack when the array has no free slot left

  • The push of the last item before the stack is emptied

  • Every push costs the same amount of work

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

Definition

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.

Before
head -> B -> A

Stack holds B on top of A

After
head -> C -> B -> A

Stack holds C on top of B on top of A

headpoints to topheadmoved to new nodeBtop itemCnew top nodeAbottom itemBApoints tonextpoints tonextnext
What happens to the head pointer and the next pointers when C is pushed onto a stack holding B and 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.

Worked example

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.

  1. 1
    Create 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.

  2. 2
    Link 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.

  3. 3
    Move the head pointer

    The head pointer is set to the new node. This is one assignment.

  4. 4
    Compare the two stacks

    The same three steps run in both cases. The count of 3 or 3,000,000 never enters the calculation.

Result

Both pushes finish within the same fixed amount of work, so the worst-case run time of push is Theta(1).

python
Notes on the code
  • 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.

Key point

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).

Pushes into freeslotseach Theta(1)Push finds array fullno free slotCreate larger arraynew storageCopy all n itemswork grows with nPlace new itemthen finish pushLater pushes intofree slotseach Theta(1) againarray fillsmust make roomcopythencontinue
What steps does the push that finds the array full take, and how does it compare with the cheap pushes around it?
Worked example

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?

  1. 1
    Detect the full array

    No free slot remains, so the item cannot be written directly into the array.

  2. 2
    Create a larger array

    A new, larger array is created to hold the items.

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

  4. 4
    Place the new item

    The fifth item is written into the next free slot of the new array.

  5. 5
    Compare with a cheap push

    A push into a free slot performs a single write, which does not depend on n.

Result

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.

OperationLinked list: worst caseArray: worst caseArray: average case
PushTheta(1)Theta(n) when the array is fullTheta(1)
PopTheta(1)Not described as expensive in the sourceNot described in the source

Run times for the two stack implementations, as stated in the source

Key point

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 fails

    The 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 instead

    Define 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 like

    Pushing onto a stack of three million items seems like it should take longer than pushing onto a stack of three items.

    Why it fails

    The operation only creates a node and updates a few links at the top. The total item count never enters the calculation.

    Do this instead

    Recognize 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 like

    A push onto an array that is already full.

    Why it fails

    When 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 instead

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

    The expensive copy does occur. The average stays bounded because the rare expensive push is spread across many cheap pushes.

    Do this instead

    Describe the average case as the cost per push over a sequence of operations, not as the absence of expensive operations.

Practice Tracing Stack Costs

MEDIUM

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.