Implementing a Stack with a Linked List
A stack can be implemented using a linked list instead of an array. The linked list stores each stack element inside a node, while one reference keeps track of the top of the stack.
Since a stack follows the Last In, First Out (LIFO) principle, the most recently added node must be the first node removed. A linked list can achieve this efficiently by treating its head node as the top of the stack.
How the Structure Works
Each node contains two pieces of information: the value stored by the stack and a reference to the next node.
Top
↓
[30 | •] → [20 | •] → [10 | null]
The top reference points to the most recently inserted node. When a new value is pushed, a new node becomes the new top. When a value is popped, the current top node is removed and the next node becomes the new top.
Creating a Node
The first step is to create a node structure that stores a value and a reference to the next node.
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
Initially, a node does not point to another node, so its
next property is
null.
The Top Reference
The stack itself needs a reference to the first node. This reference is commonly
called top.
class Stack {
constructor() {
this.top = null;
}
}
An empty stack has no nodes, so its top is initially
null.
Push Operation
The push operation adds a new value to the top of the stack. With a linked list, this can be done by creating a node and placing it before the current top node.
Suppose the stack currently looks like this:
Top
↓
[20] → [10] → null
After push(30), the new node points to the old
top and then becomes the new top:
Top
↓
[30] → [20] → [10] → null
The important sequence is to connect the new node to the current top before replacing the top reference.
push(value) {
const node = new Node(value);
node.next = this.top;
this.top = node;
}
Pop Operation
The pop operation removes the current top node and returns its value.
If the stack is:
Top
↓
[30] → [20] → [10] → null
calling pop() should return
30 and move the top reference to the next node:
Top
↓
[20] → [10] → null
The implementation only needs to save the current value, move the top reference, and return the saved value.
pop() {
if (this.top === null) {
return undefined;
}
const value = this.top.value;
this.top = this.top.next;
return value;
}
Peek Operation
The peek operation reads the value at the top without removing the node.
peek() {
if (this.top === null) {
return undefined;
}
return this.top.value;
}
Because the top reference already points directly to the first node, peek does not require traversing the linked list.
Checking Whether the Stack Is Empty
A linked-list stack is empty when its top reference is
null.
isEmpty() {
return this.top === null;
}
This check is useful before performing operations that require an existing top node, such as pop or peek.
Tracking the Stack Size
Unlike an array, a linked list does not have a built-in length property. If the stack
needs a constant-time size() operation, the stack
can maintain a counter.
class Stack {
constructor() {
this.top = null;
this.count = 0;
}
}
Increment the counter after a successful push and decrement it after a successful pop.
Complete Linked List Stack
The following implementation provides the main operations required for a practical linked-list-based stack.
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class Stack {
constructor() {
this.top = null;
this.count = 0;
}
push(value) {
const node = new Node(value);
node.next = this.top;
this.top = node;
this.count++;
}
pop() {
if (this.top === null) {
return undefined;
}
const value = this.top.value;
this.top = this.top.next;
this.count--;
return value;
}
peek() {
if (this.top === null) {
return undefined;
}
return this.top.value;
}
isEmpty() {
return this.top === null;
}
size() {
return this.count;
}
}
Using the Linked List Stack
Once the stack is implemented, using it is similar to using an array-based stack. The underlying node structure remains hidden behind the stack operations.
const stack = new Stack();
stack.push("HTML");
stack.push("CSS");
stack.push("JavaScript");
console.log(stack.peek());
// JavaScript
console.log(stack.size());
// 3
console.log(stack.pop());
// JavaScript
console.log(stack.peek());
// CSS
console.log(stack.size());
// 2
Why Push and Pop Happen at the Head
A linked list can technically use either end as the stack top, but a singly linked list is naturally efficient when operations happen at the head. Adding or removing the first node does not require traversing the remaining nodes.
If the tail were used as the top of a singly linked list, removing the last node would require finding its predecessor. That would make pop an O(n) operation. Using the head keeps both push and pop at O(1).
Step-by-Step Push
Consider a stack containing 10 and 20:
Top
↓
[20] → [10] → null
To push 30, the algorithm performs three conceptual steps:
- Create a new node containing 30.
- Make the new node point to the current top node.
- Move the top reference to the new node.
Before:
Top → [20] → [10] → null
After connecting the new node:
[30] → [20] → [10] → null
After updating top:
Top
↓
[30] → [20] → [10] → null
Step-by-Step Pop
Suppose the stack contains three nodes:
Top
↓
[30] → [20] → [10] → null
To remove 30:
- Save the value stored by the current top node.
- Move top to the current top node's next reference.
- Return the saved value.
Top
↓
[20] → [10] → null
The old top node is no longer reachable through the stack. JavaScript's garbage collector can eventually reclaim it when there are no other references to it.
Handling an Empty Stack
Calling pop or peek on an empty linked-list stack should be handled explicitly.
Returning undefined is one simple JavaScript
approach.
const stack = new Stack();
console.log(stack.pop());
// undefined
console.log(stack.peek());
// undefined
console.log(stack.isEmpty());
// true
Another design is to throw an error for invalid operations. The important point is that the behavior should be intentional and consistent with the requirements of the application.
Array Stack vs Linked List Stack
| Feature | Array Stack | Linked List Stack |
|---|---|---|
| Top location | End of array | Head node |
| Push | O(1) amortized | O(1) |
| Pop | O(1) | O(1) |
| Peek | O(1) | O(1) |
| Size | O(1) with length | O(1) with a counter |
| Extra node references | No | Yes |
Memory Considerations
Each linked-list node stores both its value and a reference to the next node. Therefore, a linked-list stack has additional per-element memory overhead compared with a simple array representation.
The advantage is that the stack does not depend on a contiguous array structure. Nodes can be created as needed, and there is no need to shift existing elements when pushing or popping from the head.
When to Use a Linked List Stack
A linked-list implementation can be useful when learning pointer-based data structures or when the application benefits from explicit node-based storage. It is also a useful implementation to understand for technical interviews because it demonstrates how references can maintain a LIFO structure without relying on built-in array methods.
- Learning linked lists and stack fundamentals
- Implementing a stack without relying on array operations
- Interview questions involving custom data structures
- Understanding constant-time insertion and removal at the head
- Building other structures that use linked nodes internally
Common Mistakes
-
Updating
topbefore connecting the new node to the previous top during push. - Traversing the entire linked list for every pop operation.
- Forgetting to update the size counter after push or pop.
-
Attempting to access
top.valuewhen top is null. - Using the tail of a singly linked list as the top and accidentally making pop O(n).
Complexity Analysis
When the head of the linked list represents the stack top, the primary stack operations do not require traversal.
| Operation | Time | Reason |
|---|---|---|
| Push | O(1) | Insert a node at the head |
| Pop | O(1) | Remove the head node |
| Peek | O(1) | Top directly references the head |
| isEmpty | O(1) | Check whether top is null |
| Size | O(1) | Use a maintained counter |
Key Takeaways
- A linked-list stack stores each element inside a node.
- The head node represents the top of the stack.
- Push inserts a new node at the head.
- Pop removes the head node and returns its value.
- Peek reads the head without removing it.
- A size counter can provide constant-time size checks.
- Push and pop are O(1) when the head is used as the stack top.
- Linked-list stacks require extra memory for node references.