The dummy-node technique
In one line
Put a fake node in front of the real head so that every real node has a predecessor; then inserting or deleting at the head needs no special case and you simply return dummy.next.
What it is
A dummy node (also called a sentinel) is an extra node that holds no real data and sits before the first real node. Your code works with dummy.next as the head, and at the end returns dummy.next, which is the correct head even if the original head was removed or replaced.
Most linked list bugs come from the head being special: it has no previous node, so 'delete the node after prev' does not work for it, and an empty result list has no tail to append to. A dummy gives every node, including the first, a predecessor, and gives an empty list a tail. Doubly linked lists often use two sentinels, one at each end.
Why interviewers ask
Interviewers like to see clean, short code without fragile if-statements for the head. Using a dummy node in Remove Nth Node From End, Remove Linked List Elements, Merge Two Lists or Partition List signals experience, reduces bugs under pressure and makes your code easier to explain.
How it works
- 1Create dummy = ListNode(0, head). Now dummy.next is the real head.
- 2Walk with a pointer that starts at dummy, not at head, and always look at cur.next. Deleting is cur.next = cur.next.next, which works even when cur.next is the real head.
- 3When building a new list, start with tail = dummy and append with tail.next = node; tail = node. The first append is no longer a special case.
- 4For removal from the end, place two pointers at dummy with a gap of n + 1 nodes; when the leading one reaches None, the trailing one is just before the node to remove.
- 5Return dummy.next as the new head. Never return dummy itself or the old head variable.
- 6In a doubly linked list, keep head and tail sentinels that always exist; every real node then has non-null prev and next, so insert and delete are four pointer updates with no checks.
Worked example · Remove the 2nd node from the end of 1 -> 2 -> 3 -> 4 -> 5
- Create dummy -> 1 -> 2 -> 3 -> 4 -> 5. Set slow = fast = dummy, n = 2.
- Move fast n + 1 = 3 steps: dummy -> 1 -> 2 -> 3. fast is at node 3; slow is at dummy.
- Move both until fast is None: (slow 1, fast 4), (slow 2, fast 5), (slow 3, fast None).
- slow is node 3, just before node 4, the 2nd from the end. Set 3.next = 5. Result: 1 -> 2 -> 3 -> 5.
- Edge case: n = 5 (remove the head). fast moves 6 steps and ends at None at once, slow stays at dummy, dummy.next = 2. Returning dummy.next gives 2 -> 3 -> 4 -> 5 with no special code.
Picture it
A bookend at the start of a shelf. Every book now has something to its left, so 'slide the book next to this one out' works the same for the very first book, and an empty shelf still has a fixed place to start adding books.
Reference code
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def remove_nth_from_end(head, n):
dummy = ListNode(0, head) # handles removing the real head
slow = fast = dummy
for _ in range(n + 1):
fast = fast.next # gap of n nodes between slow and fast
while fast:
slow, fast = slow.next, fast.next
slow.next = slow.next.next # slow is just before the target
return dummy.next
def partition(head, x):
"""Nodes < x first, then nodes >= x, keeping relative order."""
less = lt = ListNode()
more = mt = ListNode()
while head:
if head.val < x:
lt.next, lt = head, head
else:
mt.next, mt = head, head
head = head.next
mt.next = None # cut the old link to avoid a cycle
lt.next = more.next
return less.next
remove_nth_from_end works even when the head is removed, because slow can stay at the dummy. partition builds two lists behind two dummies, joins them, and sets mt.next = None so the old links cannot form a cycle. Both are O(n) time and O(1) extra space.
Key patterns
Delete matching nodes
When: Remove all nodes with a value, or remove duplicates from a sorted list
How: cur = dummy; if cur.next matches, cur.next = cur.next.next, else cur = cur.next.
Build a result list
When: Merge, partition, add two numbers
How: tail = dummy; append with tail.next = node; tail = tail.next; return dummy.next.
Gap pointers from the dummy
When: Remove the n-th node from the end
How: Start both at dummy, advance fast n + 1 steps, then move both; slow ends just before the target.
Two sentinels in a doubly linked list
When: LRU cache, deque, browser history
How: Fixed head and tail sentinels so every real node has prev and next; no null checks on insert or delete.
Multiple dummies
When: Partition or odd-even split
How: One dummy per output list; join them at the end and terminate the last list with None.
Real-world use
Linux kernel list_head
Kernel lists are circular doubly linked lists whose head is a sentinel list_head with no data; an empty list is just the head pointing to itself, so add and delete never check for null.
C++ std::list
Common implementations of std::list, including libstdc++, use a sentinel node that marks both the beginning and the end, which is what end() points to, so insertion and erase need no head or tail special cases.
Python OrderedDict and lru_cache
The pure-Python OrderedDict in CPython's collections module keeps a sentinel root node in a circular doubly linked list, and functools.lru_cache uses a root sentinel the same way, so moving or removing entries is always the same few pointer updates.
In a project
A moderation tool filters a linked list of comments, removing any flagged ones. Bug reports show that when the first comment is flagged, the whole thread disappears or the flagged comment stays visible.
- Read the code: it starts with cur = head and checks cur.next, so the head is never checked, and there is a separate branch for the head that mishandles consecutive flagged comments.
- Rewrite with dummy = ListNode(0, head) and cur = dummy; while cur.next exists, skip flagged nodes with cur.next = cur.next.next, otherwise advance.
- Return dummy.next as the new head of the thread.
- Add tests: all comments flagged, first only, last only, consecutive flagged, empty thread.
Impact: The bug class disappears because there is no head special case left, the function shrinks to a few lines, and the new tests lock in the behaviour for every edge case.
Trade-offs
Use it when
- The head may be removed or replaced
- You are building a new list node by node
- You maintain a doubly linked list with frequent inserts and deletes at the ends
Avoid it when
- Pure read-only traversals, where there is nothing to rewire
- Memory-critical embedded code where an extra node per list matters (rare)
- Code that exposes nodes to callers who might mistake the sentinel for real data; hide it behind an API
Cost: O(1) extra space (one node per dummy); no change to the time complexity of the algorithm.
Common mistakes
- Returning head instead of dummy.next, which returns a removed node when the head was deleted.
- Starting the walk at head instead of dummy, so the first node is never checked for removal.
- Advancing cur after a deletion, which skips the next node and misses consecutive matches.
- Forgetting to set the last tail's next to None when building lists from existing nodes, creating a cycle.
- Moving fast only n steps instead of n + 1 in remove-nth-from-end, so slow stops on the target instead of before it.
Interview questions
Quick revision
- dummy = ListNode(0, head); work from dummy; return dummy.next.
- Every real node now has a predecessor: no head special case.
- Delete: cur.next = cur.next.next, do not advance after deleting.
- Build: tail = dummy; tail.next = node; tail = node.
- Remove n-th from end: fast moves n + 1 from dummy.
- Doubly linked list: head and tail sentinels, no null checks.
- Terminate built lists with tail.next = None.
Check yourself
1. Why return dummy.next instead of head?
2. In remove-nth-from-end starting both pointers at dummy, how many steps does fast move first?
3. While deleting all nodes equal to 6 from 6 -> 6 -> 1, what goes wrong if you advance cur after each deletion?
4. What does a doubly linked list with head and tail sentinels guarantee?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.