Linked List Operations
Pointer-based structures with no random access — every operation is a traversal, and mastery is nothing more than clean pointer manipulation. Three moves (dummy node, fast/slow, prev/curr/next) cover almost every interview question.
A chain of pointers, nothing more
A linked list is a sequence of nodes where each node knows only its value and where the next node lives. There is no index arithmetic and no O(1) jumps — if you want the 5th node, you walk five steps.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
How to recognize this pattern
- The problem explicitly involves a linked list.
- You need to reverse, merge, detect a cycle, or reorder nodes.
- You need the middle node or the nth from the end.
- The head node might change — insertion or deletion at the front.
The four moves
Move 1Dummy node
Use when the head might change — deletion, insertion, merging. Without it,
removing the head is a special case; with it, every node is handled uniformly and you
return dummy.next at the end.
dummy = ListNode(0)
dummy.next = head
# ... do work ...
return dummy.next
Move 2Two pointers (fast/slow)
slow moves one step per turn, fast moves two. When fast hits the end,
slow is at the middle. The same idea with a fixed gap instead of a speed
difference finds the nth node from the end in one pass.
Move 3Iterative reversal (prev / curr / next)
The single most important linked-list skill. Walk the list once; at each node,
save the next pointer, flip the current node's arrow backwards, then advance
both prev and curr. Step through it below.
def reverse_list(head):
prev = None
curr = head
while curr:
next_node = curr.next # Save next
curr.next = prev # Reverse pointer
prev = curr # Advance prev
curr = next_node # Advance curr
return prev # New head
curr.next before saving it,
the rest of the list is gone forever. This four-line loop is worth memorizing verbatim.
Move 4Recursion
Natural for reversal, merging, and divide-and-conquer on lists. Recurse to the end, then rewire pointers as the stack unwinds:
def reverse_list(head):
if not head or not head.next:
return head
new_head = reverse_list(head.next) # Reverse the rest
head.next.next = head # Point next node back to us
head.next = None # Remove old forward pointer
return new_head
Which move for which situation
| Situation | Technique |
|---|---|
| Head might change | Dummy node |
| Find middle / detect cycle | Fast/slow pointers |
| Find nth from end | Two pointers with n gap |
| Reverse all or part of list | prev/curr/next iteration |
| Merge two sorted lists | Dummy node + compare loop |
| Complex reordering | Split + reverse + merge |
The problems the moves compose into
LC 21Merge Two Sorted Lists
Dummy node + compare loop. Whoever has the smaller head donates a node; attach the survivor at the end.
def merge_two_lists(list1, list2):
dummy = ListNode(0)
curr = dummy
while list1 and list2:
if list1.val <= list2.val:
curr.next = list1
list1 = list1.next
else:
curr.next = list2
list2 = list2.next
curr = curr.next
curr.next = list1 or list2 # Attach remaining
return dummy.next
LC 19Remove Nth Node from End
Build a gap of n + 1 from the dummy so that when fast falls off the end,
slow sits on the node before the one to delete.
def remove_nth_from_end(head, n):
dummy = ListNode(0, head)
slow = fast = dummy
# Advance fast by n+1 steps
for _ in range(n + 1):
fast = fast.next
# Move both until fast hits None
while fast:
slow = slow.next
fast = fast.next
# slow is right before the node to remove
slow.next = slow.next.next
return dummy.next
LC 141Detect Cycle — Floyd's Algorithm
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
LC 142Find the Cycle Start
After slow and fast meet inside the cycle: reset one pointer to the head, move both at speed 1, and they meet exactly at the cycle's entry point. The proof is three lines of algebra:
def detect_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
# Found cycle, now find start
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None
LC 2Add Two Numbers
Dummy node + a running carry. The loop condition l1 or l2 or carry
handles unequal lengths and a final carry in one stroke.
def add_two_numbers(l1, l2):
dummy = ListNode(0)
curr = dummy
carry = 0
while l1 or l2 or carry:
val = carry
if l1:
val += l1.val
l1 = l1.next
if l2:
val += l2.val
l2 = l2.next
carry, digit = divmod(val, 10)
curr.next = ListNode(digit)
curr = curr.next
return dummy.next
LC 143Reorder List — the three-step combo
Transform 1 → 2 → 3 → 4 → 5 into 1 → 5 → 2 → 4 → 3.
This is the moves composed: find middle + reverse second half + merge alternating.
def reorder_list(head):
if not head or not head.next:
return
# Step 1: Find middle
slow, fast = head, head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Step 2: Reverse second half
prev, curr = None, slow.next
slow.next = None # Cut the list
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
# Step 3: Merge alternating
first, second = head, prev
while second:
tmp1, tmp2 = first.next, second.next
first.next = second
second.next = tmp1
first = tmp1
second = tmp2
LC 234Palindrome Linked List
Same combo, different payoff: find the middle, reverse the back half, compare the halves front-to-front.
def is_palindrome(head):
# Find middle
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev = None
while slow:
next_node = slow.next
slow.next = prev
prev = slow
slow = next_node
# Compare both halves
left, right = head, prev
while right:
if left.val != right.val:
return False
left = left.next
right = right.next
return True
Complexity reference
| Operation / Problem | Time | Space |
|---|---|---|
| Reverse linked list | O(n) | O(1) |
| Find middle | O(n) | O(1) |
| Detect cycle | O(n) | O(1) |
| Find cycle start | O(n) | O(1) |
| Merge two sorted lists | O(n + m) | O(1) |
| Remove nth from end | O(n) | O(1) |
| Add two numbers | O(max(m, n)) | O(max(m, n)) |
| Reorder list | O(n) | O(1) |
| Palindrome linked list | O(n) | O(1) |
| Merge k sorted lists (heap) | O(N log k) | O(k) |
Where pointers go wrong
curr.next before saving it orphans the rest of the list.
Always next_node = curr.next first.
n + 1
(starting from the dummy), not n.
slow.next = None and the two halves stay
connected — your merge loops forever.
Practice ladder
In this order — easiest first. Tick each one as you solve it, and re-solve anything that took more than 25 minutes two days later.