Dummy Head / Sentinel Node
Overview
The dummy head (also called a sentinel node) is a technique where you add an extra node before the real head of a linked list. This node does not hold meaningful data; it exists only to simplify pointer logic. By using a dummy, you avoid special-case code for an empty list or for operations that change the head (e.g. insert at front, delete the first node).
Why it matters: Without a dummy, you often need separate branches: "if the list is empty" or "if we're deleting the head." With a dummy, every node (including the original head) has a "previous" node, so the same pointer-update logic applies everywhere.
When to Use a Dummy Head
Use a dummy head when:
- Inserting or deleting at the front β the head pointer would otherwise change; with a dummy, you only update
dummy.next. - Building a new list β you can append to
tailwhile keepingtail = dummyinitially; no "is this the first node?" check. - Removing nodes that match a condition β e.g. "remove all nodes with value X"; a single pass with
prevandcurrworks, andprevcan start at the dummy. - Merging or rearranging lists β you build the result from a dummy and return
dummy.next.
How It Works
- Create a dummy node:
const dummy = new ListNode(-1);(or any placeholder value). - Set
dummy.next = headso the real list hangs off the dummy. - Use a pointer (e.g.
prevortail) that starts atdummyand moves along the list as you do your logic. - When done, return
dummy.nextas the new head (which may benullif the list became empty).
Key idea: The dummy is the "previous" node for the original head, so operations at the front are no longer special.
Example 1: Remove All Nodes With a Given Value
Problem: Given the head of a linked list and an integer val, remove all nodes whose value equals val and return the new head.
Without a dummy, you'd need a loop to skip past any leading nodes with value val, then a second loop for the rest. With a dummy, one pass suffices.
function removeElements(head: ListNode | null, val: number): ListNode | null {
const dummy = new ListNode(-1);
dummy.next = head;
let prev: ListNode = dummy;
let curr: ListNode | null = head;
while (curr !== null) {
if (curr.val === val) {
prev.next = curr.next;
curr = curr.next;
} else {
prev = curr;
curr = curr.next;
}
}
return dummy.next;
}prevalways points to the last "kept" node (initially the dummy).- When
curr.val === val, we skipcurrby settingprev.next = curr.next; no special case when the node to remove is the head. - Time: O(n), Space: O(1).
Example 2: Merge Two Sorted Lists (Building a New List)
Problem: Merge two sorted linked lists and return the head of the merged list.
We build the result list by repeatedly appending the smaller of the two current heads. A dummy gives us a single place to append and avoids "is this the first node?" checks.
function mergeTwoLists(
l1: ListNode | null,
l2: ListNode | null
): ListNode | null {
const dummy = new ListNode(-1);
let tail = dummy;
while (l1 !== null && l2 !== null) {
if (l1.val <= l2.val) {
tail.next = l1;
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
tail = tail.next;
}
tail.next = l1 !== null ? l1 : l2;
return dummy.next;
}tailstarts at the dummy and moves to the last node we appended.- We always set
tail.nextto the chosen node and then advancetail; no branch for "first node." - Time: O(n + m), Space: O(1) (we only change pointers).
Example 3: Insert a Node at the Head
Problem: Insert a new node with value val at the beginning of the list and return the new head.
With a dummy, "insert at head" is the same as "insert after dummy."
function insertAtHead(head: ListNode | null, val: number): ListNode | null {
const dummy = new ListNode(-1);
dummy.next = head;
const newNode = new ListNode(val);
newNode.next = dummy.next;
dummy.next = newNode;
return dummy.next;
}Even when head is null, dummy.next becomes the new node and newNode.next becomes null, so the result is correct. No separate "if (head === null)" branch.
Example 4: Remove the Nth Node From the End (Two Pointers + Dummy)
Problem: Remove the nth node from the end of the list and return the head. (If we need to remove the first node, the head changes.)
A dummy ensures that when we use a "previous" pointer, it exists even when the node to remove is the head.
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
const dummy = new ListNode(-1);
dummy.next = head;
let fast: ListNode | null = dummy;
let slow: ListNode | null = dummy;
for (let i = 0; i <= n && fast !== null; i++) {
fast = fast.next;
}
while (fast !== null) {
fast = fast.next;
slow = slow!.next;
}
if (slow?.next != null) {
slow.next = slow.next!.next;
}
return dummy.next;
}fastmoves n+1 steps ahead ofslow, so whenfasthits the end,slowis the predecessor of the nth-from-end node.- Deleting is
slow.next = slow.next.next. If the node to delete is the head,slowis the dummy, so we correctly updatedummy.nextand return the new head. - Time: O(n), Space: O(1).
Example 5: Swap Nodes in Pairs
Problem: Given a linked list, swap every two adjacent nodes and return the new head. (If the list has an odd length, the last node stays.)
We need a "previous" pointer for the pair. Using a dummy, the first pair is handled like every other pair.
function swapPairs(head: ListNode | null): ListNode | null {
const dummy = new ListNode(-1);
dummy.next = head;
let prev: ListNode = dummy;
while (prev.next !== null && prev.next.next !== null) {
const first = prev.next;
const second = prev.next.next;
prev.next = second;
first.next = second.next;
second.next = first;
prev = first;
}
return dummy.next;
}prevpoints to the node before the current pair. We swapfirstandsecondand then setprev = first(the node that is now second in the pair) to move to the next pair.- Time: O(n), Space: O(1).
Edge Cases to Handle
- Empty list β
head === null. With a dummy,dummy.nextisnull; after your logic, returningdummy.nextcorrectly yieldsnull. - Single node β e.g. remove that node;
prevstays at dummy,prev.nextbecomesnull, sodummy.nextisnull. - All nodes removed β e.g. remove all nodes with value
valand every node had that value; againdummy.nextbecomesnull. - Head changes β any operation that would change the head is handled because the "previous" of the old head is the dummy; you never need to reassign a separate
headvariable.
Common Mistakes
- Forgetting to return
dummy.nextβ You must return the node after the dummy, notheadordummy. - Losing the reference to the list β Keep
prev(ortail) and update it correctly so you don't break the list when rewiring. - Using the dummy's value β The dummy's value is irrelevant; only
dummy.nextand the fact that it's a valid "previous" node matter. - Not advancing
prev(ortail) β In remove/build logic, advance the pointer that represents "last kept node" or "last appended node" so the next iteration sees the right place.
Time and Space Complexity
- Time: Usually one pass, O(n) (or O(n + m) for two lists).
- Space: O(1) β only a fixed number of pointers (dummy, prev, curr, etc.); the dummy is one node.
Summary
- A dummy head is an extra node before the real head so that the first node has a "previous" node like any other.
- Use it to avoid special cases for empty list and for operations at the front.
- Pattern: Create dummy β set
dummy.next = headβ run your logic with a pointer starting at dummy β returndummy.next. - Apply this in remove-by-value, merge two lists, insert at head, remove nth from end, swap pairs, and many other linked-list problems.
For more patterns that build on this, see Fast and Slow Pointers and Reverse In-Place.