Linked-list operations: insert, remove and preserve the chain
Extend the existing node-and-traversal resource with complete modification algorithms. Trace references in their actual assignment order, then test the edge cases that can break the chain.
Content owner: Michael Print · Written for A-Level learners · Checked against official specifications
The idea to start with
A singly linked list keeps a head reference and a next reference in each node. Traversal follows those references until None. Insertion connects the new node to the successor before redirecting the predecessor; deletion redirects the predecessor around the removed node.
The head needs separate treatment because it has no predecessor. An empty list has head None, and deleting its only node must restore that empty state. The code below specifies that searches and value-based modifications use the first matching value.
Create and traverse a complete singly linked list.
Find a node and distinguish a missing value from an empty list.
Insert at the head, tail and after a selected value.
Remove the first matching node while handling empty and single-node lists.
Make the representation and operation contracts explicit
head → 4 → 6 → 8 → None contains separate Node objects. References link them; they are not array indexes and need not occupy adjacent memory.
Traversal copies head into a local reference, then follows next without changing head. The constructor works backwards through input values and prepends them, preserving input order in O(n) time.
An array-of-records version could store next indexes and manage a free list. That is a different storage implementation of the same chain.
Duplicates are allowed. find returns the first matching Node or None.
insert_after inserts after the first match and returns True; an absent target returns False without a change.
remove_first removes one matching occurrence, preserving any later duplicate.
Insert without losing the existing successor
Preserve the old successor before overwriting its link. Otherwise the remainder of the chain may become unreachable.
Prepending builds a node pointing to the old head, then replaces head. Empty lists work because that old head is None.
Appending traverses to a node whose next is None, then connects the new tail. For an empty list, set head instead.
Insert 5 after 4 without losing 6
1
Before
head → 4 → 6 → 8 → None. Create a new node containing 5.
2
Preserve the successor
Set new.next to the existing node containing 6. The old chain is still reachable.
3
Redirect the predecessor
Set 4.next to the new node containing 5.
4
After
head → 4 → 5 → 6 → 8 → None.
Remove by retaining a predecessor reference
Keep current for the inspected node and previous for its predecessor. previous is None at the head. Check current exists before reading current.value.
After reconnecting, clear the removed node’s next to show it is detached. Another reference may still keep that object alive, so detaching alone does not guarantee memory release.
If search reaches None, return False without changing the list. Traversal, search and locating a target can take O(n); relinking a known predecessor takes O(1).
insert_after by value remains O(n) because it searches first. Prepend is O(1); append is O(n) here because no tail reference is stored. Each node stores a value and next reference.
Which surviving link must change?
Remove a non-head node
Set previous.next = current.next, bypassing current.
Remove the head
Set head = current.next. No predecessor exists.
Remove the only node
Its next is None, so the head update restores an empty list.
Worked example
Run every basic operation on the same list
Construct [4, 6, 8]; values follows head and each next reference, returning that order.
Insert 5 after 4, prepend 3, and append 9. The chain is now [3, 4, 5, 6, 8, 9].
Remove the first 4. Previous points at 3 and current at 4, so reconnect 3 directly to 5. The remaining values are [3, 5, 6, 8, 9].
find(8) returns its Node, whose value is 8. remove_first(99) returns False and leaves the chain unchanged.
Create a singleton [7] and remove 7. Head becomes None, values returns [], and a second removal returns False. These checks exercise a boundary that a middle-node trace misses.
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
class LinkedList:
def __init__(self, values):
self.head = None
for value in reversed(values):
self.prepend(value)
def values(self):
result = []
current = self.head
while current is not None:
result.append(current.value)
current = current.next
return result
def find(self, value):
current = self.head
while current is not None:
if current.value == value:
return current
current = current.next
return None
def prepend(self, value):
self.head = Node(value, self.head)
def append(self, value):
if self.head is None:
self.prepend(value)
return
current = self.head
while current.next is not None:
current = current.next
current.next = Node(value)
def insert_after(self, target, value):
predecessor = self.find(target)
if predecessor is None:
return False
predecessor.next = Node(value, predecessor.next)
return True
def remove_first(self, value):
previous = None
current = self.head
while current is not None:
if current.value == value:
if previous is None:
self.head = current.next
else:
previous.next = current.next
current.next = None
return True
previous = current
current = current.next
return False
chain = LinkedList([4, 6, 8])
print(chain.values())
chain.insert_after(4, 5)
chain.prepend(3)
chain.append(9)
chain.remove_first(4)
print(chain.values())
print(chain.find(8).value, chain.remove_first(99))
single = LinkedList([7])
print(single.remove_first(7), single.values())
print(single.remove_first(7))
Worked example
Trace deletion at the tail
For [3, 5, 9], search for 9. At the first comparison, previous is None and current holds 3.
After two advances, previous holds 5 and current holds 9. The target matches, and current.next is None.
Assign previous.next to None. The node containing 5 becomes the tail; head still references 3.
Detach current and return True. A later traversal gives [3, 5]. No array elements shift; the link changes determine the logical order.
Original A-Level practice
5 original questions total 22 marks. Attempt each before opening the independently written indicative marking guidance.
Question 1
4 marks
A list is head → 2 → 7 → 10 → None. Describe the reference updates, in order, to insert 5 after 2. Explain why that order matters.
Show solution and marking guidance+
Indicative answer
Create the node containing 5 (1). Set its next to the existing node containing 7 (1), then redirect 2.next to the new node (1). Preserving the old successor before overwriting the predecessor's link keeps the remainder reachable (1).
Question 2
4 marks
Trace remove_first(7) on [7]. Give previous, current.next, the resulting head and the returned value.
Show solution and marking guidance+
Indicative answer
previous is None (1). current.next is None (1). The head is set to None (1), and the method returns True (1).
Question 3
4 marks
Run remove_first(5) conceptually on [3, 5, 5, 8]. Give the resulting values and explain how the contract determines the answer. Then state the result of remove_first(99).
Show solution and marking guidance+
Indicative answer
The resulting values are [3, 5, 8] (1). Only the first matching 5 is removed (1), preserving the second duplicate (1). Removing 99 returns False without another change (1).
Question 4
5 marks
A programmer removes a node by assigning current = current.next and returns True. Explain why this fails and show the required alternatives for a head and a non-head node.
Show solution and marking guidance+
Indicative answer
The assignment moves only the local reference (1); the chain still points at the target (1). For the head use head = target.next (1). Otherwise use previous.next = target.next (1), where previous was retained during traversal (1).
Question 5
5 marks
Compare the worst-case time for prepend, find and this implementation's append. Explain why insert_after by value cannot generally be described as O(1).
Show solution and marking guidance+
Indicative answer
Prepend is O(1) (1), find is O(n) (1), and append is O(n) because no tail reference is stored (1). insert_after first performs a potentially linear search (1), even though its final link changes take O(1) (1).
Specification and references
This guide addresses OCR H446 1.4.2(b–c), 2.3.1(e): singly linked-list operations. Check your examination year and the complete specification for the assessment scope.
These are independently written explanations and practice questions. CompSciTutoring.co.uk is not affiliated with or endorsed by an examination board. The marking guidance is indicative; always check the syllabus for your examination year.