Overview
A singly linked list is a chain of nodes where each node stores a value and a pointer to the next node. The list is identified by its head; the last node points to null. Unlike arrays, nodes are scattered in memory, so there is no index arithmetic: reaching the kth node means walking k steps.
The payoff is cheap structural change. Once you hold a reference to a node, inserting or removing the node after it is O(1), because you only rewire pointers. Almost every linked list interview problem is about rewiring pointers correctly without losing part of the list.
Each clue tells you where the next clue is hidden. To reach clue 5 you must follow clues 1 through 4. Adding a new clue between two others only requires changing what one clue points to; nothing else moves.
When to use it
- Frequent insertions and deletions at the front, or next to a node you already hold.
- You do not need random access by index.
- Implementing stacks, queues, adjacency lists, or hash map buckets.
- The problem gives you a ListNode head (most linked list interview questions).
Problem patterns it solves
Recognize it when: reverse all or part of a list, check palindrome, reorder.
- 206. Reverse Linked List
- 92. Reverse Linked List II
- 25. Reverse Nodes in k-Group
- 234. Palindrome Linked List
Recognize it when: the head itself might be removed or changed.
- 203. Remove Linked List Elements
- 21. Merge Two Sorted Lists
- 82. Remove Duplicates from Sorted List II
Recognize it when: middle node, cycle detection, nth from the end.
- 876. Middle of the Linked List
- 141. Linked List Cycle
- 19. Remove Nth Node From End of List
Recognize it when: merge sorted lists, sort a list, partition around a value.
- 21. Merge Two Sorted Lists
- 148. Sort List
- 86. Partition List
- 23. Merge k Sorted Lists
Recognize it when: numbers stored digit by digit; carry handling.
- 2. Add Two Numbers
- 445. Add Two Numbers II
Where it is used in real software
Java's HashMap stores colliding entries in a linked list per bucket (converted to a red-black tree when a bucket grows past 8 entries).
Free lists in malloc implementations link together available memory blocks so allocation and freeing are O(1).
Each block or commit stores a pointer (hash) to its parent, forming a linked list back to the first one.
Simple undo stacks and persistent data structures share list tails between versions without copying.
Key terms
- Node
- An object with val and next.
- Head
- Reference to the first node; null for an empty list.
- Tail
- The last node, whose next is null.
- Dummy node
- A placeholder before the head so edge cases at the head disappear.
- Traversal
- Following next pointers from head until null: O(n).
Reversing a list in place
- 1Start with prev = null, curr = head
prev will become the new head; curr is the node being processed.
- 2Save next
next = curr.next, because you are about to overwrite curr.next and would lose the rest of the list.
- 3Reverse the pointer
curr.next = prev.
- 4Advance both
prev = curr; curr = next.
- 5Return prev
When curr is null, prev is the old tail, now the new head.
STEP 1prev = null, curr = 1. Links: 1 -> 2 -> 3 -> 4 -> null.
Remove all nodes with value 6 using a dummy head
head = 6 -> 1 -> 6 -> 2, val = 6
| Step | prev | prev.next | Action | List from dummy |
|---|---|---|---|---|
| 1 | dummy | 6 | remove: dummy.next = 1 | dummy -> 1 -> 6 -> 2 |
| 2 | dummy | 1 | keep: prev = 1 | dummy -> 1 -> 6 -> 2 |
| 3 | 1 | 6 | remove: 1.next = 2 | dummy -> 1 -> 2 |
| 4 | 1 | 2 | keep: prev = 2 | dummy -> 1 -> 2 |
| 5 | 2 | null | stop | return dummy.next = 1 |
NOWStep: 1 | prev: dummy | prev.next: 6 | Action: remove: dummy.next = 1 | List from dummy: dummy -> 1 -> 6 -> 2
The dummy node made removing the original head identical to removing any other node, so there is no special case.
Implementation
class ListNode { constructor(val, next = null) { this.val = val; this.next = next; }} function reverseList(head) { let prev = null; let curr = head; while (curr) { const next = curr.next; // save the rest curr.next = prev; // reverse the link prev = curr; curr = next; } return prev;} function removeElements(head, val) { const dummy = new ListNode(0, head); let prev = dummy; while (prev.next) { if (prev.next.val === val) prev.next = prev.next.next; else prev = prev.next; } return dummy.next;} function mergeTwoLists(a, b) { const dummy = new ListNode(0); let tail = dummy; while (a && b) { if (a.val <= b.val) { tail.next = a; a = a.next; } else { tail.next = b; b = b.next; } tail = tail.next; } tail.next = a ?? b; return dummy.next;} function addTwoNumbers(l1, l2) { const dummy = new ListNode(0); let tail = dummy, carry = 0; while (l1 || l2 || carry) { const sum = (l1?.val ?? 0) + (l2?.val ?? 0) + carry; carry = Math.floor(sum / 10); tail.next = new ListNode(sum % 10); tail = tail.next; l1 = l1?.next; l2 = l2?.next; } return dummy.next;}Complexity and performance
Must walk from the head.
Rewire the head pointer.
Only one pointer changes.
You need the previous node, which requires a walk.
No binary search possible.
Trade-offs
Linked lists avoid shifting on insert, but each node carries pointer overhead and poor cache locality, so a simple scan is often several times slower than on an array.
Singly linked lists use less memory but cannot move backward or delete a node in O(1) without its predecessor.
Recursive solutions are elegant but use O(n) stack space and can overflow on long lists.
Variants and related techniques
Adds a prev pointer for O(1) removal of any node and backward traversal.
The tail points back to the head; used for round-robin scheduling and the Josephus problem.
Multiple levels of forward pointers give O(log n) search; used in Redis sorted sets.
Common mistakes
- Losing the rest of the list.
Fix: Save curr.next before overwriting it.
- Null pointer errors at the end.
Fix: Check curr and curr.next before dereferencing; with fast pointers check fast && fast.next.
- Special-casing the head.
Fix: Use a dummy node and return dummy.next.
- Returning the old head after reversing.
Fix: Return prev, the new head.
Interview questions
How do you find the middle of a linked list in one pass?
Move slow one step and fast two steps. When fast reaches the end, slow is at the middle.
Why use a dummy node?
It gives every real node a predecessor, so inserting or deleting at the head uses the same code as anywhere else.
Can you delete a node given only that node?
If it is not the tail, copy the next node's value into it and bypass the next node. This is O(1) but changes node identity.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 206. Reverse Linked List | Easy | Iterative and recursive reversal. |
| 21. Merge Two Sorted Lists | Easy | Dummy node and tail pointer. |
| 203. Remove Linked List Elements | Easy | Dummy head removal. |
| 2. Add Two Numbers | Medium | Carry propagation. |
| 92. Reverse Linked List II | Medium | Partial reversal. |
| 25. Reverse Nodes in k-Group | Hard | Reverse in blocks. |