Topics › Stacks and Queues › Learning
Implement stack using Linkedlist
Java source ↗ Edit this article ↗
Jump to code:
Problem
Design a stack of integers (push, pop, top, isEmpty, size) whose storage is a singly linked list rather than an array. The stack must grow without a fixed capacity and release memory as elements are popped. Last in, first out: the most recently pushed element is the one pop removes and top reports.
Constraints: up to 10^5 operations, values fit in int. pop and top on an empty stack are errors; the code throws NoSuchElementException.
Example 1
push(1), push(2), push(3), top(), pop(), pop(), size(), isEmpty()
output: 3, 3, 2, 1, false // 3 was pushed last, so it comes out first
Example 2
push(4), pop(), push(8), top(), pop(), isEmpty()
output: 4, 8, 8, true // a stack that is emptied must be reusable
The list is built from this node type, and both implementations below satisfy the same interface so one test script drives them:
static class Node {
int val;
Node next;
Node(int val) {
this.val = val;
}
}
interface IntStack {
void push(int x);
int pop();
int top();
boolean isEmpty();
int size();
}
Approach 1: The tail is the top
Intuition. When people first draw a stack as a list they often put the oldest element first and append new elements at the end, so the list reads bottom to top. That works, but a singly linked list only knows where its head is. To push, we must walk from the head to the last node to attach the new one. To pop, we must walk to the second-to-last node so we can cut the last one off. To read the top, we walk to the end again. Every operation visits every node.
Algorithm.
push(x): create a node. If the list is empty it becomes the head; otherwise walkcurfromheaduntilcur.next == nulland setcur.nextto the new node. Incrementcount.pop(): if empty, throw. If the head is the only node, remember its value and sethead = null. Otherwise walkcuruntilcur.next.next == null, remembercur.next.val, setcur.next = null. Decrementcount.top(): walk to the last node and return its value.isEmpty()ishead == null;size()iscount.
static class TailLinkedStack implements IntStack {
private Node head; // bottom of the stack
private int count = 0;
public void push(int x) {
Node node = new Node(x);
if (head == null) {
head = node;
} else {
Node cur = head;
while (cur.next != null) cur = cur.next; // walk to the tail
cur.next = node;
}
count++;
}
public int pop() {
if (head == null) throw new NoSuchElementException("stack underflow");
count--;
if (head.next == null) { // single node: the tail is the head itself
int x = head.val;
head = null;
return x;
}
Node cur = head;
while (cur.next.next != null) cur = cur.next; // stop at the second-last node
int x = cur.next.val;
cur.next = null; // unlink the old tail
return x;
}
public int top() {
if (head == null) throw new NoSuchElementException("stack is empty");
Node cur = head;
while (cur.next != null) cur = cur.next;
return cur.val;
}
public boolean isEmpty() {
return head == null;
}
public int size() {
return count;
}
}
Complexity. Time O(n) for push, pop and top, where n is the current size, because each walks the entire list. Space O(n) for the nodes, plus one reference per node for next.
A tail pointer would make push O(1), but pop would still be O(n) in a singly linked list: once the tail is removed, the new tail is the node before it, and the only way to find that node is to walk from the head. Doubly linked nodes fix this at the cost of a second reference per node. The cleaner fix is to put the top at the other end.
Approach 2: The head is the top
Intuition. The one place a singly linked list can insert and delete in constant time is its head. So let the head be the top of the stack. Pushing links the new node in front of the current head; popping unlinks the head and moves to head.next; top reads head.val. No traversal is ever needed, and because popped nodes are no longer referenced, the garbage collector reclaims them, so the stack shrinks as well as grows.
Algorithm.
push(x): create a node, setnode.next = head, thenhead = node. Incrementcount.pop(): ifhead == null, throw. Rememberhead.val, sethead = head.next, decrementcount, return the value.top(): if empty throw, else returnhead.val.isEmpty()ishead == null;size()iscount.
static class HeadLinkedStack implements IntStack {
private Node head; // top of the stack
private int count = 0;
public void push(int x) {
Node node = new Node(x);
node.next = head; // new node points at the old top
head = node;
count++;
}
public int pop() {
if (head == null) throw new NoSuchElementException("stack underflow");
int x = head.val;
head = head.next; // the old top is now unreachable and gets collected
count--;
return x;
}
public int top() {
if (head == null) throw new NoSuchElementException("stack is empty");
return head.val;
}
public boolean isEmpty() {
return head == null;
}
public int size() {
return count;
}
}
Dry run on example 1 (list written top → bottom):
| operation | list | count | returns |
|---|---|---|---|
| push(1) | 1 | 1 | |
| push(2) | 2 → 1 | 2 | |
| push(3) | 3 → 2 → 1 | 3 | |
| top() | 3 → 2 → 1 | 3 | 3 |
| pop() | 2 → 1 | 2 | 3 |
| pop() | 1 | 1 | 2 |
| size() | 1 | 1 | 1 |
| isEmpty() | 1 | 1 | false |
Complexity. Time O(1) for every operation: each one touches only the head node and the count field. Space O(n) for n stored elements, with no wasted capacity and no copying when the stack grows.
Compared with an array-backed stack, the linked version never needs to resize and never over-allocates, but each element costs an object header and a next reference, and nodes are scattered in memory rather than contiguous. For a stack of primitives the array usually wins on speed; the linked list wins when sizes are unpredictable or when elements are large objects you would rather not copy.
Edge cases and pitfalls
- Order of the two assignments in
push.node.next = headmust come beforehead = node. Reversed, the new node points to itself and the rest of the list is lost. - Popping the last node. In approach 2 nothing special happens:
head = head.nextsetsheadtonull. In approach 1 the single-node case must be handled separately, becausecur.next.nextwould dereferencenull. - Empty stack. Check
head == nullbefore readinghead.valorhead.next; otherwise you get aNullPointerExceptioninstead of a meaningful error. - Tracking
size. Counting nodes on demand isO(n). Keep acountfield and update it inpushandpop; the test script checkssize()after every random operation. - Reuse after emptying. After
popempties the stack,pushmust work again. With the head-as-top design this is automatic becausepushnever assumes a non-empty list.
Related problems
- Implement Stack using Arrays is the array-backed alternative with the resize trade-off.
- Implement queue using Linkedlist uses the same
Nodebut must manage both ends. - Implement Stack using Queue builds a stack from a very different primitive.
- Reverse a Stack and Sort a stack using recursion manipulate a stack using only
pushandpop, regardless of how it is stored.