Remove Linked List Elements: Dummy Head Deletion
Remove Linked List Elements is LeetCode problem 203. You are given the head of a linked list and an integer val. Remove all nodes where Node.val == val and return the new head.
Example: [1, 2, 6, 3, 4, 5, 6] with val = 6 becomes [1, 2, 3, 4, 5]. An empty list returns empty. A list like [7, 7, 7, 7] with val = 7 returns empty.
The catch is that the target value might appear at the head of the list. If you delete the head, you need to return a different node as the new head. Handling this as a special case is messy. A dummy node eliminates the special case entirely.
The approach
Create a dummy node that points to the original head. Then use a prev pointer starting at the dummy node. At each step, look at prev.next:
- If
prev.next.val == val, the next node needs to go. Setprev.next = prev.next.nextto skip over it. Do not advanceprev, because the newprev.nextmight also need to be removed. - If
prev.next.val != val, the next node is safe. Advanceprevtoprev.next.
Keep going until prev.next is null. Then return dummy.next, which is the new head.
The dummy node is the key insight. Because prev starts before the real head, you can delete the head node using the exact same logic as deleting any other node. No special cases needed.
Visual walkthrough
Trace through removing val = 6 from the list 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 -> null. The dummy node (D, yellow) anchors the front. Watch the prev pointer (purple) as it walks through the list, skipping nodes that match the target value.
Setup: Create dummy node. prev = dummy.
Dummy node (D) points to head. prev starts at the dummy node.
Check prev.next (node 1): val is 1, not 6. Advance prev.
Node 1 is safe. Move prev to node 1.
Check prev.next (node 2): val is 2, not 6. Advance prev.
Node 2 is safe. Move prev to node 2.
Check prev.next (node 6): val is 6. Remove it!
Set prev.next = prev.next.next, skipping the 6. prev stays at node 2.
Check prev.next (node 3): val is 3, not 6. Advance prev.
Node 3 is safe. Move prev to node 3.
Check prev.next (node 4): val is 4, not 6. Advance prev.
Node 4 is safe. Move prev to node 4.
Check prev.next (node 5): val is 5, not 6. Advance prev.
Node 5 is safe. Move prev to node 5.
Check prev.next (node 6): val is 6. Remove it!
Set prev.next = prev.next.next (null), removing the trailing 6. prev stays at node 5.
Done: prev.next is null. Return dummy.next.
Both 6s removed. Return dummy.next, which is node 1. Result: 1 -> 2 -> 3 -> 4 -> 5 -> null.
Notice how prev only advances when prev.next is safe. When a match is found, prev stays put and the matching node gets bypassed. This ensures consecutive matches are caught even if they appear back to back.
Python solution
def removeElements(head, val):
dummy = ListNode(0)
dummy.next = head
prev = dummy
while prev.next:
if prev.next.val == val:
prev.next = prev.next.next
else:
prev = prev.next
return dummy.next
Step-by-step explanation
- Create the dummy node. It points to the original head. This gives you a stable anchor that never gets deleted.
- Set
prev = dummy. The prev pointer will always sit one node behind the node being examined. - Loop while
prev.nextexists. You are always checking the node ahead ofprev, notprevitself. - If
prev.next.val == val, splice it out withprev.next = prev.next.next. The node is effectively removed from the chain.prevstays where it is so the newprev.nextgets checked on the next iteration. - If
prev.next.val != val, the node is safe. Moveprevforward by one. - Return
dummy.next. The dummy itself is never part of the result. Itsnextpointer gives you the true head of the cleaned-up list.
Complexity analysis
Time: O(n). You visit each node exactly once. Every node is either skipped (removed) or passed over (prev advances). The total number of operations across all iterations is at most n.
Space: O(1). You only allocate one dummy node and one pointer variable. The removal happens in-place by rewiring existing pointers. No extra data structures are needed.
The building blocks
This problem is built from two core building blocks:
Dummy head for safe deletion. When a deletion might affect the head node, prepending a dummy node lets you treat every node uniformly. You never need a separate check for "is this the head?" The dummy absorbs that edge case.
This pattern appears in many linked list problems:
- Remove Duplicates from Sorted List II (LeetCode 82) deletes all nodes with duplicate values. A dummy head is essential because the original head might have duplicates.
- Partition List (LeetCode 86) builds two sublists with dummy heads, then merges them.
- Any problem where the head might be deleted benefits from this technique.
Pointer-based node removal. You keep a prev pointer one step behind the node under inspection. To remove a node, you set prev.next = prev.next.next. This is the fundamental linked list deletion operation.
This shows up everywhere:
- Remove Duplicates from Sorted List (LeetCode 83) uses the same
prev.next = prev.next.nextsplice but checks for matching adjacent values instead of a target value. - Remove Nth Node From End of List (LeetCode 19) finds the node to remove with a two-pointer gap, then splices it out the same way.
Once you have the dummy-head-plus-prev-pointer pattern in muscle memory, any linked list deletion problem becomes a variation of the same idea.
Edge cases
- Empty list (
headis null).dummy.nextis null from the start. The while loop never runs. Return null. - All nodes match the target (e.g.,
[7, 7, 7, 7]withval = 7). Every node gets spliced out.prevnever advances past the dummy.dummy.nextends up as null. Return null. - No nodes match the target.
prevadvances through every node without splicing. The list is returned unchanged. - Target is at the head. The dummy node handles this.
previs at the dummy,prev.nextis the head, and it gets spliced out just like any other node. - Target is at the tail. When
prevreaches the second-to-last node,prev.next.val == val, so you setprev.next = prev.next.next(which is null). The tail is cleanly removed.
Without the dummy node, you would need a separate while loop at the top to handle cases where the head itself matches val. The dummy node collapses that special case into the main loop. Whenever a problem says "remove nodes from a linked list," reach for a dummy head first.
From understanding to recall
This is a short solution with simple logic. You will read it and feel confident. But in an interview, the details matter: do you create the dummy node before or after setting prev? Do you advance prev inside the if or the else? Do you return dummy.next or head?
These small decisions are exactly the kind of thing that slips under pressure. The fix is not to reread the solution. It is to type it from scratch, repeatedly, until the structure is automatic. Spaced repetition turns understanding into recall. After a few reps, you will not hesitate on any of those choices.
Related posts
- Remove Duplicates from Sorted List - Same pointer-based deletion technique, applied to removing duplicate values instead of a target value
- Reverse Linked List - Another fundamental linked list operation where pointer manipulation order determines correctness