Edge cases are inputs that sit near the boundary of an algorithm’s assumptions. In linked list problems, these cases usually involve empty lists, one-node lists, changes at the head, changes at the tail, repeated values, cycles, shared nodes, or invalid positions.
A linked list algorithm is often correct for the middle of the list before it is correct for the boundaries. This section gives a checklist for finding and handling those boundaries.
Empty List
The empty list has no nodes.
head = nullEvery traversal must check this case before reading head.value or head.next.
if head == null:
return nullOperations such as search, reverse, merge, and split should define clear behavior on an empty input.
reverse(null) = null
merge(null, list) = list
split(null) = (null, null)One-Node List
A one-node list is both head and tail.
head -> nullMany algorithms should return the same node unchanged.
if head == null or head.next == null:
return headThis guard appears in reversal, merge sort, palindrome checks, and middle-based splitting.
Two-Node List
A two-node list exposes off-by-one errors.
a -> b -> nullExamples:
reverse: b -> a -> null
split halves: a -> null, b -> null
middle second convention: b
middle first convention: aTesting only empty, one-node, and long lists can miss bugs in this case.
Deleting the Head
Without a dummy node, deleting the head requires direct update of head.
head = head.nextWith a dummy node, the same deletion rule works everywhere.
dummy.next = head
prev = dummy
cur = headAfter deletion, return:
dummy.nextThe common error is changing a local pointer but forgetting to update the external head reference.
Deleting the Tail
The tail is the last node. In a singly linked list, deleting it requires finding its predecessor.
prev.next = nullIf the structure stores a tail pointer, update it too.
tail = prevIf the deleted node was the only node, both head and tail become null.
Inserting at the Head
Insert before the first node by updating head.
new.next = head
head = newWith a dummy node:
new.next = dummy.next
dummy.next = newAgain, return dummy.next.
Inserting at the Tail
Insert after the last node by setting its next.
tail.next = new
tail = new
new.next = nullIf the list was empty, both head and tail must point to new.
Repeated Values
Repeated values can cause algorithms to remove too little or too much.
For example:
1 -> 2 -> 2 -> 2 -> 3 -> nullDeleting all 2 values requires keeping the predecessor fixed while matching nodes are removed.
if cur.value == target:
prev.next = cur.next
cur = prev.next
else:
prev = cur
cur = cur.nextDo not advance prev after a deletion.
All Nodes Removed
Deletion by predicate may remove every node.
2 -> 2 -> 2 -> nullAfter deletion, the result must be:
nullA dummy node handles this case cleanly because dummy.next may become null.
No Nodes Removed
The opposite case should preserve the original structure.
1 -> 3 -> 5 -> nullIf deleting even values, the result is unchanged. The algorithm must still advance correctly and terminate.
Invalid Position
Position-based operations need a defined policy.
Examples:
insert at position greater than length
delete at position greater than or equal to length
delete nth from end when n > lengthPossible policies:
| Policy | Behavior |
|---|---|
| Reject | return error |
| Clamp | insert at nearest valid boundary |
| Ignore | return list unchanged |
| Extend | create missing positions, rarely appropriate |
The policy should be explicit before implementation.
Cycles
Many linked list algorithms assume that traversal reaches null. If the input may contain a cycle, ordinary traversal can loop forever.
while cur != null:
cur = cur.nextCycle-sensitive code should detect cycles first or maintain a visited set.
Shared Nodes
Two lists may share a suffix.
l1: a -> b -> c -> d
l2: x -> y -> c -> dMutating the shared suffix through one list changes the other. Algorithms that promise not to modify inputs should copy nodes rather than relink them.
Checklist
Before accepting a linked list solution, test these cases:
| Case | Example |
|---|---|
| Empty | null |
| One node | a |
| Two nodes | a -> b |
| Delete head | remove a from a -> b |
| Delete tail | remove b from a -> b |
| Insert at head | insert before a |
| Insert at tail | insert after b |
| Consecutive matches | 1 -> 2 -> 2 -> 3 |
| All removed | 2 -> 2 -> 2 |
| None removed | 1 -> 3 -> 5 |
| Odd length | a -> b -> c |
| Even length | a -> b -> c -> d |
| Invalid position | k > length |
| Cycle | a -> b -> c -> b |
| Shared suffix | two heads reach same tail |
Key Insight
Edge cases are not exceptional in linked list algorithms. They are consequences of pointer structure. A robust solution defines behavior at the boundaries first, then uses sentinels, invariants, and clear ownership rules to make the general case and the boundary case follow the same logic.