Linked List Techniques (Dummy Node, In-Place Reversal)
Linked lists are where clean algorithmic thinking goes to get tangled in null checks. The logic of "reverse this stretch" or "delete this node" is trivial; the bugs all live at the edges, when the node you are touching is the head, or the tail, or the list is empty. Two small techniques defuse almost all of that pain: a dummy (sentinel) node that removes the head as a special case, and in-place reversal that rewires pointers without allocating anything. Neither is deep. Both are the difference between code that works and code that works except on the first element.
In-place reversal: the pointer dance
Reversing a singly linked list looks like it should need a second list, but it does not. You walk the list once, flipping each next pointer to face backward, carrying three references so you never lose your place.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse(head):
prev = None
while head:
nxt = head.next # remember where we were headed
head.next = prev # flip this link to face backward
prev = head # slide both cursors forward
head = nxt
return prev # prev is the new head
The nxt stash is the whole trick: the instant you overwrite head.next, the rest of the list would be lost, so you grab it first. O(n) time, O(1) space, and the same three-pointer dance reverses a sublist too, once you have pointers to its edges.
In the wild: the sentinel nodes of an LRU cache
The dummy-node idea earns its keep in one of the most common structures in real systems: the least-recently-used cache. An LRU keeps entries in a doubly linked list ordered by recency, moving an entry to the front whenever it is used and evicting from the back when full. Both operations are O(1), but only if you never have to ask "is this the head or the tail." The trick is to start the list with two permanent sentinel nodes, a head and a tail that hold no data and never move.
class Node:
def __init__(self, key=None, val=None):
self.key, self.val = key, val
self.prev = self.next = None
class RecencyList:
def __init__(self):
self.head, self.tail = Node(), Node() # sentinels, always present
self.head.next, self.tail.prev = self.tail, self.head
def add_front(self, node):
node.prev, node.next = self.head, self.head.next
self.head.next.prev = self.head.next = node
def remove(self, node):
node.prev.next, node.next.prev = node.next, node.prev
Look at what is missing: there is not a single if node is None or "is this the head" branch. Because the sentinels are always there, every real node is guaranteed to have a real prev and next, so add_front and remove are just four pointer assignments with no edge cases. That is the same lesson as the reversal, one level up: a tiny bit of structure, a stashed pointer or a pair of dummy nodes, buys you code with no special cases and no extra memory. It is why production caches, and plenty of kernel data structures, are built on sentinels.
The trigger
A singly or doubly linked list problem where the messy part is manipulating nodes at the boundaries, deleting the head, reversing a segment, splicing near the ends. If you find yourself writing a special case for "when it is the first node," reach for a dummy node. If you need to reverse without extra space, reach for the three-pointer walk.
Where it shows up
- Reversal: whole list, a sublist between two positions, or in fixed-size groups.
- Deletion and insertion at the head: remove the Nth node from the end, dedup a sorted list, all cleaner with a dummy before the head.
- Ordered structures with O(1) splicing: LRU caches and other recency or priority lists built on doubly linked nodes.
Where it bites
The bugs are the ones you would expect from manual pointer surgery: overwrite a next before stashing it and you orphan the rest of the list; forget to update a prev in a doubly linked list and it silently corrupts in one direction only, which is miserable to debug. Reversal boundaries are their own hazard, connecting the reversed segment back to the nodes on either side is where off-by-one lives.
When it is the wrong tool
Reach for a different structure entirely if you need random access, because a linked list makes "the k-th element" an O(n) walk where an array is O(1). If you are reversing only in order to read the list backward once, storing the values in an array or a stack is simpler than rewiring pointers. In most application code you should lean on library containers rather than hand-rolling nodes at all; these techniques matter most in interviews and in low-level systems, allocators, kernels, intrusive lists, where an O(1) splice or zero allocation genuinely pays.
Its neighbors
It leans directly on Fast & Slow Pointers, which locate the middle or a cycle before you rewire anything, and shares its pointer-juggling mindset with Two Pointers. The recency-list example is the beating heart of the LRU cache you will meet again in Design Problems.
References
- Introduction to Algorithms (CLRS), 4th ed., Cormen, Leiserson, Rivest, Stein, 2022
- The Algorithm Design Manual, 3rd ed., Steven Skiena, 2020