Linked Lists
Master pointer manipulation with singly linked lists. Learn reversal, dummy head technique, fast/slow pointers, and the two-pointer gap strategy — fundamental building blocks for dozens of interview problems.
Learn & Reference
Understanding the Pattern
Linked Lists Pattern
A linked list is a linear data structure where each element (node) contains a value and a pointer to the next node. Unlike arrays, linked lists don't support random access — you must traverse from the head to reach any node. This constraint makes pointer manipulation the central skill.
Core Concept
Every node has two fields: val (the data) and next (a pointer to the next node, or null at the end). The list is accessed through a head pointer. Losing the head means losing the entire list.
head → [1] → [2] → [3] → [4] → null
The key insight: you can restructure a linked list in O(1) space by redirecting pointers. No shifting, no copying — just rewiring connections.
Key Techniques
1. Iterative Reversal
The most fundamental linked list operation. Use three pointers: prev, curr, and next. At each step, save curr.next, point curr.next to prev, then advance both pointers.
Before: prev → null, curr → [1] → [2] → [3]
Step 1: null ← [1] curr → [2] → [3]
Step 2: null ← [1] ← [2] curr → [3]
Step 3: null ← [1] ← [2] ← [3] curr → null
Reversal is a subroutine in many problems: reorder list, palindrome check, reverse k-group.
2. Dummy Head (Sentinel Node)
Create a fake node before the real head: dummy.next = head. This eliminates edge cases when the head itself might change (e.g., removing the first node, merging two lists where either could come first).
At the end, return dummy.next as the new head. This tiny trick simplifies code significantly.
3. Fast/Slow Pointers (Floyd's Algorithm)
Two pointers move at different speeds — slow moves one step, fast moves two steps per iteration.
Find the middle node: When fast reaches the end, slow is at the middle. This is O(n) time, O(1) space — no need to count the length first.
Detect a cycle: If there's a cycle, fast will eventually lap slow and they'll meet. If fast reaches null, there's no cycle. This is the foundation of Floyd's cycle detection algorithm.
4. Two-Pointer Gap Technique
To find the nth node from the end in one pass: advance a fast pointer n steps ahead, then move both slow and fast together. When fast reaches the end, slow is at the target.
This avoids needing to know the list length. Combined with a dummy head, it cleanly handles removing the nth node from the end.
5. Merge Technique
Merging two sorted lists: compare the heads of both lists, take the smaller one, advance that pointer. Use a dummy head to avoid special-casing the first node. This is the same merge used in merge sort and is the building block for "merge k sorted lists."
When Linked Lists Fail
- You need random access by index → use an array
- You need binary search → use an array (no O(1) index access in linked lists)
- The data fits in memory and you need cache-friendly traversal → arrays are faster due to spatial locality
- You need to traverse backward → use a doubly linked list or reverse first
Common Mistakes
- Losing the head reference: If you advance the
headpointer during traversal, you can never get back to the start. Always use a separatecurrpointer for traversal and keephead(ordummy) untouched - Null pointer errors: Before accessing
node.next, check thatnodeis not null. Before accessingnode.next.next, check bothnodeandnode.next. Fast/slow pointer loops needfast != null && fast.next != null - Forgetting to update next pointers: When removing or inserting a node, every relevant
nextpointer must be updated. Drawing the before/after diagram on paper prevents most bugs - Off-by-one in the gap technique: To remove the nth node from the end, advance
fastby n+1 (or use a dummy head and advance by n). Getting this off by one either skips or includes the wrong node - Creating an unintended cycle: When reversing or reordering, if you don't null-terminate the end of a sublist, you create a cycle that causes infinite loops during traversal
When to Use
Template
1# --- ListNode Definition ---2class ListNode:3def __init__(self, val=0, next=None):4self.val = val5self.next = next67# --- Reverse a Linked List ---8def reverse_list(head):9prev, curr = None, head10while curr:11nxt = curr.next # save next12curr.next = prev # reverse pointer13prev = curr # advance prev14curr = nxt # advance curr15return prev # prev is the new head1617# --- Find Middle (for splitting) ---18def find_middle(head):19slow = fast = head20while fast and fast.next:21slow = slow.next22fast = fast.next.next23return slow2425# --- Merge Two Sorted Lists (Dummy Head) ---26def merge(l1, l2):27dummy = ListNode(0)28curr = dummy29while l1 and l2:30if l1.val <= l2.val:31curr.next = l132l1 = l1.next33else:34curr.next = l235l2 = l2.next36curr = curr.next37curr.next = l1 or l238return dummy.next
Syntax Reference
1# --- Python: ListNode Definition ---2class ListNode:3def __init__(self, val=0, next=None):4self.val = val5self.next = next67# --- Traversal ---8curr = head9while curr:10print(curr.val)11curr = curr.next1213# --- Insert at head ---14new_node = ListNode(val)15new_node.next = head16head = new_node1718# --- Delete next node ---19if curr.next:20curr.next = curr.next.next2122# --- Dummy head (sentinel) ---23dummy = ListNode(0)24dummy.next = head25# ... modify list ...26return dummy.next
Common Recipes
1# --- Reverse a Linked List ---2def reverse_list(head):3prev, curr = None, head4while curr:5nxt = curr.next6curr.next = prev7prev = curr8curr = nxt9return prev1011# --- Find Middle Node (slow/fast pointers) ---12def find_middle(head):13slow = fast = head14while fast and fast.next:15slow = slow.next16fast = fast.next.next17return slow # middle node1819# --- Detect Cycle (Floyd's Algorithm) ---20def has_cycle(head):21slow = fast = head22while fast and fast.next:23slow = slow.next24fast = fast.next.next25if slow == fast:26return True27return False2829# --- Merge Two Sorted Lists (Dummy Head) ---30def merge_sorted(l1, l2):31dummy = ListNode(0)32curr = dummy33while l1 and l2:34if l1.val <= l2.val:35curr.next = l136l1 = l1.next37else:38curr.next = l239l2 = l2.next40curr = curr.next41curr.next = l1 or l242return dummy.next
Complexity
| Operation | Time | Space |
|---|---|---|
| Access by index | O(n) | O(1) |
| Search for value | O(n) | O(1) |
| Insert at head | O(1) | O(1) |
| Insert at tail (without tail pointer) | O(n) | O(1) |
| Delete a node (given previous) | O(1) | O(1) |
| Reverse entire list | O(n) | O(1) |
| Find middle (fast/slow) | O(n) | O(1) |
| Detect cycle (Floyd) | O(n) | O(1) |
| Merge two sorted lists | O(n + m) | O(1) |
Watch Out
- ✗Losing the head reference: If you advance the `head` pointer during traversal, you can never get back to the start. Always use a separate `curr` pointer for traversal and keep `head` (or `dummy`) untouched
- ✗Null pointer errors: Before accessing `node.next`, check that `node` is not null. Before accessing `node.next.next`, check both `node` and `node.next`. Fast/slow pointer loops need `fast != null && fast.next != null`
- ✗Forgetting to update next pointers: When removing or inserting a node, every relevant `next` pointer must be updated. Drawing the before/after diagram on paper prevents most bugs
- ✗Off-by-one in the gap technique: To remove the nth node from the end, advance `fast` by n+1 (or use a dummy head and advance by n). Getting this off by one either skips or includes the wrong node
- ✗Creating an unintended cycle: When reversing or reordering, if you don't null-terminate the end of a sublist, you create a cycle that causes infinite loops during traversal