Card 04/ 09
Forgetting to Update the Tail
Deleting the last node leaves the tail pointing at a node that is no longer in the list.
A singly linked list that keeps a tail reference, to its last node, has to move that reference whenever the last node changes, and a delete that forgets breaks the append after it. This card covers the symptom, why it happens, what it costs and the line that prevents it.
The symptom shows up one operation late. A list holds 3 7 5. A delete removes the 5 by setting the 7's next to null, the marker for no next node, and does not touch the tail. Then 8 is appended.
Before the run: what does the list print from the head after the 8 is appended?
Only 3 7. The 8 is attached, but to the deleted 5, which the list no longer reaches.
A run printed the list from the head and from the tail after each operation:
Without the fix: the delete leaves the tail behind
Start
The head reaches all three nodes, and the tail refers to the last of them.
head -> [3] -> [7] -> [5] -> null
tail -> [5] -> nullDelete 5 without moving the tail
The walk from the head now stops at the 7, and the tail still refers to the 5.
head -> [3] -> [7] -> null
tail -> [5] -> nullAppend 8
Append sets the tail's next to the new node and moves the tail to it. The 8 lands after the 5, outside the list.
head -> [3] -> [7] -> null
tail -> [8] -> null
node 5 -> [5] -> [8] -> nullThe trigger is the delete, and the mechanism is append's trust in the tail: it never walks from the head, so it cannot notice that the tail is stale. Nothing fails at the moment of the mistake. Every later append vanishes in the same way, the head and the tail now describe two different chains, and the bug surfaces far from the delete that caused it, as data that is silently missing.
The remedy is a rule: every operation that can change the last node also moves the tail. Deleting the last node moves it to the node before; inserting after the tail moves it to the new node; deleting the only node sets both head and tail to null. The same run with the fix:
after delete 5, with the tail moved to the 7:
head -> [3] -> [7] -> null
tail -> [7] -> null
after append 8:
head -> [3] -> [7] -> [8] -> null
tail -> [8] -> nullTo check a list in a test, walk from the head and confirm that the walk ends at the node the tail refers to.
← → move through sections and cards