Linked lists
A linked list keeps its order with arrows instead of positions. Each node holds a value and a pointer to the next node. There's no index, so you walk from the front, but adding or removing a node is just changing an arrow or two.
- Sam Linked lists. Python doesn't even have one built in.
- Priya True, but interview problems hand you one constantly, and real systems use them inside queues and caches. A linked list keeps its order with arrows instead of positions. Each node holds a value and a pointer to the next node.
- Priya It's a treasure hunt. Each clue tells you where the next one is hidden. To reach clue five you follow clues one to four; there's no shortcut. But adding a clue is easy: rewrite one clue to point at the new one, and make the new one point where the old one used to.
- Sam Head, next, None?
- Priya Head is the first node you hold on to. Next is each node's arrow. The last node's next is None. A doubly linked list adds a prev arrow, so you can walk both ways.
- Sam Why can't I jump to node five?
- Priya The nodes can be anywhere in memory. You only learn where node five is when node four tells you. So reaching the i-th node is linear.
- Sam And inserting?
- Priya Two arrows change, in a strict order. First point the new node at the rest of the list, then point the old node at the new one. Do it the other way and the rest of the list is lost.
- Sam Costs compared with an array?
- Priya Inserting or removing at the head, or right after a node you already have: constant, where an array shifts everything. Reading the i-th value or searching: linear.
- Priya Your turn. Which is constant time for a linked list but linear for an array: reading the i-th value, inserting at the head, or searching?
- Priya Inserting at the head. The array shifts every element; the list changes one arrow.
- Sam What do I actually write?
- Priya A small node class with a value and next. Java has a doubly linked LinkedList, and Python's deque is a ready-made doubly linked queue, but problems still use a ListNode.
- Sam The players?
- Priya Walk the list with a finger, adding up the values. Insert after a node, linking the new node first. And delete a node with a dummy in front, so deleting the head is no special case.
- Sam Why a dummy node?
- Priya Every real node then has a node before it. You stop one before the node to delete, point past it, and return dummy dot next at the end.
- Sam Memory hook?
- Priya The treasure hunt. Walk with a finger, link the new one first, stop one early to delete.
- Sam Problems?
- Priya Convert a binary number in a linked list to an integer: double the value and add each bit. And remove linked list elements, where you only move forward when the next node is a keeper.
- Sam Mistakes?
- Priya Losing the rest of the list by changing an arrow too early. Moving head itself. Forgetting the empty list. Reading next dot val on the last node. And infinite loops from a missing step forward.
- Sam Next?
- Priya Lesson six, stacks: last in, first out.
1. What it is
A linked list is a chain of nodes. Each node stores a value and next, a reference to the following node; the last node's next is None (null). You hold on to the first node, the head. A doubly linked list also gives each node a prev pointer, so you can walk both ways.
- Fast: inserting or removing at the head, or right after a node you already have:
O(1), no shifting. - Slow: reaching the i-th node or searching:
O(n), you walk from the head. - Where you meet them: interview problems hand you a
head; real systems use them inside queues, LRU caches and hash-map buckets.
2. How it sits in memory
Unlike an array, the nodes can be anywhere in memory. Each one remembers the address of the next. That's why you can't jump to node i: you only know where node i is once node i − 1 tells you.
Inserting after a node changes two arrows, and the order matters: first point the new node at the rest of the list, then point the old node at the new one. Do it the other way round and the rest of the list is lost.
3. Operations and what they cost
| Operation | Cost | Compared with an array |
|---|---|---|
| Read the i-th value | O(n) | Array: O(1) |
| Insert or remove at the head | O(1) | Array: O(n) (everything shifts) |
| Insert or remove after a node you have | O(1) | Array: O(n) |
| Add at the tail | O(1) if you keep a tail pointer, else O(n) | Array: O(1) amortised |
| Search for a value | O(n) | Array: O(n) |
| Remove a node you have (doubly linked) | O(1) | Its prev pointer finds the node before it. |
dummy.next = head), work after it, and return dummy.next: the head is no longer a special case.4. In Python, TypeScript and Java
Python and TypeScript have no built-in singly linked list, so interview problems define the node class for you. Java has LinkedList (doubly linked), but problems still use a ListNode.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
head = ListNode(1, ListNode(2, ListNode(3))) # 1 → 2 → 3
node = head
while node: # walk to the end
print(node.val)
node = node.next
from collections import deque # a ready-made doubly linked queue
q = deque([1, 2, 3]); q.appendleft(0); q.pop()
class ListNode {
val: number; next: ListNode | null;
constructor(val = 0, next: ListNode | null = null) { this.val = val; this.next = next; }
}
const head = new ListNode(1, new ListNode(2, new ListNode(3)));
let node: ListNode | null = head;
while (node) { console.log(node.val); node = node.next; }class ListNode {
int val; ListNode next;
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
ListNode head = new ListNode(1, new ListNode(2, new ListNode(3, null)));
for (ListNode node = head; node != null; node = node.next) System.out.println(node.val);
LinkedList<Integer> list = new LinkedList<>(List.of(1, 2, 3)); // doubly linked
list.addFirst(0); list.removeLast();5. Core operations, step by step
Three moves cover almost every linked-list problem: walk it, splice a node in, and cut a node out.
A · Walk the list
next from the head until it reaches None, doing something at each node.O(n) time, O(1) space.The code. node is your finger on the current clue.
def total(head):
s = 0
node = head
while node:
s += node.val
node = node.next
return s
function total(head: ListNode | null): number {
let s = 0;
let node = head;
while (node) {
s += node.val;
node = node.next;
}
return s;
}static int total(ListNode head) {
int s = 0;
ListNode node = head;
while (node != null) {
s += node.val;
node = node.next;
}
return s;
}Line by line.
| Line | What it does | Why |
|---|---|---|
while node: | Until we fall off the end. | The last node's next is None, which stops the loop. |
node = node.next | Move to the next node. | Forget this line and the loop never ends. |
Watch it run. 4 → 7 → 1 → 3.
Watch out for:
- Moving head itself.
head = head.nextloses the start of the list. Walk with a separate variable. - Reading node.next.val without checking node.next. The last node's next is None.
B · Insert after a node
prev by changing two pointers.O(1) once you have prev (finding it may cost O(n)).The code. New node first points at the rest; then the old node points at the new one.
def insert_after(head, target, val):
prev = head
while prev.val != target:
prev = prev.next
node = ListNode(val)
node.next = prev.next
prev.next = node
return head
function insertAfter(head: ListNode, target: number, val: number): ListNode {
let prev = head;
while (prev.val !== target) {
prev = prev.next!;
}
const node = new ListNode(val);
node.next = prev.next;
prev.next = node;
return head;
}static ListNode insertAfter(ListNode head, int target, int val) {
ListNode prev = head;
while (prev.val != target) {
prev = prev.next;
}
ListNode node = new ListNode(val, null);
node.next = prev.next;
prev.next = node;
return head;
}Line by line.
| Line | What it does | Why |
|---|---|---|
node.next = prev.next | The new node points at the rest of the list. | Do this first, while prev.next still knows where the rest is. |
prev.next = node | The old node points at the new one. | Swapping these two lines makes node point at itself and loses the rest. |
Watch it run. Insert 9 after 7 in 4 → 7 → 1.
Watch out for:
- Linking in the wrong order. Set the new node's next first, then the old node's next.
C · Delete a node (with a dummy in front)
val by pointing the node before it past it.O(n) to find it, O(1) to remove it.The code. A dummy node in front means deleting the head needs no special case.
def delete_value(head, val):
dummy = ListNode(0, head)
prev = dummy
while prev.next and prev.next.val != val:
prev = prev.next
if prev.next:
prev.next = prev.next.next
return dummy.next
function deleteValue(head: ListNode | null, val: number): ListNode | null {
const dummy = new ListNode(0, head);
let prev = dummy;
while (prev.next && prev.next.val !== val) {
prev = prev.next;
}
if (prev.next) {
prev.next = prev.next.next;
}
return dummy.next;
}static ListNode deleteValue(ListNode head, int val) {
ListNode dummy = new ListNode(0, head);
ListNode prev = dummy;
while (prev.next != null && prev.next.val != val) {
prev = prev.next;
}
if (prev.next != null) {
prev.next = prev.next.next;
}
return dummy.next;
}Line by line.
| Line | What it does | Why |
|---|---|---|
dummy = ListNode(0, head) | A fake node in front of the head. | Now every real node, even the head, has a node before it. |
while prev.next and prev.next.val != val | Stop at the node before the one to delete. | You need the node before it to change its arrow. |
prev.next = prev.next.next | Point past the unwanted node. | Nothing points at it any more; it's gone. |
return dummy.next | The real head. | If the old head was deleted, dummy.next is the new head. |
Watch it run. Delete 1 from 4 → 7 → 1 → 3.
Watch out for:
- Stopping on the node to delete. In a singly linked list you can't reach the node before it any more. Stop one early.
- Returning head instead of dummy.next. Wrong when the head itself was deleted.
6. How to remember it
Picture
Paper clues scattered around a park, each with an arrow drawn to the next.
Chant
"Walk with a finger, link the new one first, stop one early to delete."
Key rule
The head might change? Put a dummy node in front.
7. Problems solved with it
Two easy problems using exactly the moves above.
LC 1290 · Convert Binary Number in a Linked List to Integer
O(n) time, O(1) space.The code. Operation A with a different job at each node.
def get_decimal_value(head):
value = 0
node = head
while node:
value = value * 2 + node.val
node = node.next
return value
function getDecimalValue(head: ListNode | null): number {
let value = 0;
let node = head;
while (node) {
value = value * 2 + node.val;
node = node.next;
}
return value;
}static int getDecimalValue(ListNode head) {
int value = 0;
ListNode node = head;
while (node != null) {
value = value * 2 + node.val;
node = node.next;
}
return value;
}Line by line.
| Line | What it does | Why |
|---|---|---|
value = value * 2 + node.val | Shift left one binary place and add the new bit. | The same idea as reading a decimal number digit by digit: value × 10 + digit. |
Watch it run. 1 → 0 → 1 → 1 (binary 1011).
Watch out for:
- Collecting the bits in a string first. Works, but the running value needs no extra space.
LC 203 · Remove Linked List Elements
val and return the new head.O(n) time, O(1) space.The code. Only move prev forward when its next node is a keeper.
def remove_elements(head, val):
dummy = ListNode(0, head)
prev = dummy
while prev.next:
if prev.next.val == val:
prev.next = prev.next.next
else:
prev = prev.next
return dummy.next
function removeElements(head: ListNode | null, val: number): ListNode | null {
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;
}static ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(0, head);
ListNode prev = dummy;
while (prev.next != null) {
if (prev.next.val == val) {
prev.next = prev.next.next;
} else {
prev = prev.next;
}
}
return dummy.next;
}Line by line.
| Line | What it does | Why |
|---|---|---|
if prev.next.val == val | Is the next node unwanted? | Look one ahead so you can still change prev's arrow. |
prev.next = prev.next.next | Skip it, but don't move prev. | The new next might also need deleting (6, 6). |
prev = prev.next | A keeper: step forward. |
Watch it run. Remove 6 from 6 → 1 → 6 → 6 → 2.
Watch out for:
- Moving prev after a deletion. Two unwanted nodes in a row: the second is skipped over and survives.
More practice, solved on the pattern pages
| # | Problem | Level | Where |
|---|---|---|---|
| 206 | Reverse Linked List | Easy | Solved step by step on 05 · LinkedList In-place Reversal |
| 21 | Merge Two Sorted Lists | Easy | Solved step by step on 05 · LinkedList In-place Reversal |
| 83 | Remove Duplicates from Sorted List | Easy | Solved step by step on 05 · LinkedList In-place Reversal |
| 876 | Middle of the Linked List | Easy | Solved step by step on 04 · Fast & Slow Pointers |
| 141 | Linked List Cycle | Easy | Solved step by step on 04 · Fast & Slow Pointers |
| 234 | Palindrome Linked List | Easy | Solved step by step on 04 · Fast & Slow Pointers |
8. Common mistakes
prev.next before saving where it pointed, and everything after is unreachable. Link the new node first, or keep the old next in a variable.node = head; keep head pointing at the start.head can be None. A dummy node, or an early if not head: return …, handles it.node.next.val crashes on the last node. Check node.next first.node = node.next, or a cycle you created by mistake, never ends. Draw the arrows on paper before coding.9. Self-check quiz
Reading the 1,000th value of a linked list costs…
Which order inserts node after prev safely?
node.next point at node itself and loses the rest.Why put a dummy node in front of the head?
dummy.next at the end.Which is O(1) for a linked list but O(n) for an array?
10. Where it shows up next
- 04 · Fast & Slow Pointers: two fingers at different speeds find the middle and detect cycles.
- 05 · LinkedList In-place Reversal: reversing all or part of a list by flipping arrows.
- 19 · Trie & Design: an LRU cache is a hash map plus a doubly linked list.
Next structure: 06 · Stacks, last in, first out.