Implement Stack Using a Linked List

Learn Implement Stack Using a Linked List with practical examples, explanations, best practices, and real-world coding techniques.

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:

  1. Create a new node containing 30.
  2. Make the new node point to the current top node.
  3. 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:

  1. Save the value stored by the current top node.
  2. Move top to the current top node's next reference.
  3. 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 top before 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.value when 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.

Code Examples

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() {
    return this.top ? this.top.value : undefined;
  }

  isEmpty() {
    return this.top === null;
  }

  size() {
    return this.count;
  }
}

const stack = new Stack();

stack.push(10);
stack.push(20);
stack.push(30);

console.log(stack.peek()); // 30
console.log(stack.pop());  // 30
console.log(stack.size()); // 2
    
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class Stack {
  constructor() {
    this.top = null;
  }

  push(value) {
    const node = new Node(value);

    node.next = this.top;
    this.top = node;

    console.log("Pushed:", value);
  }

  pop() {
    if (this.top === null) {
      return undefined;
    }

    const value = this.top.value;
    this.top = this.top.next;

    console.log("Popped:", value);
    return value;
  }
}

const stack = new Stack();

stack.push("A");
stack.push("B");
stack.push("C");

stack.pop();
stack.pop();

// Pushed: A
// Pushed: B
// Pushed: C
// Popped: C
// Popped: B
    
class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class Stack {
  constructor() {
    this.top = null;
  }

  push(value) {
    const node = new Node(value);
    node.next = this.top;
    this.top = node;
  }

  pop() {
    if (!this.top) {
      return undefined;
    }

    const value = this.top.value;
    this.top = this.top.next;

    return value;
  }

  isEmpty() {
    return this.top === null;
  }
}

const tasks = new Stack();

tasks.push("Send email");
tasks.push("Generate report");
tasks.push("Deploy application");

while (!tasks.isEmpty()) {
  console.log("Processing:", tasks.pop());
}

// Processing: Deploy application
// Processing: Generate report
// Processing: Send email
    

Pro Tip

For a singly linked list stack, always use the head as the top. This keeps both push and pop at O(1); using the tail would require traversing the list to remove the previous node.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.