Designing a Flexible (Linked) List in Python
Start here Now we build our own linked list with a Python class called
Node. We write four operations: check if empty, append at the end, insert at the start, and delete a value. Each one has a small trick — especially insert, because we are not allowed to change where the head points.
The Node class
A list is a chain of nodes. Each node stores a value and a link to the next node.
class Node:
def __init__(self, v = None):
self.value = v # the stored value
self.next = None # link to the next node (None = no next node)
return
def isempty(self):
if self.value == None:
return True
else:
return FalseHow we represent lists:
| Code | What it creates |
|---|---|
l1 = Node() | An empty list (one node with value None) |
l2 = Node(5) | A singleton list holding just 5 |
l1.isempty() | True |
l2.isempty() | False |
Design choice: an empty list is not None — it is a node whose value is None. That way, every list (even an empty one) is a Node object, and we can always call methods on it.
Append — add v at the end
Idea:
- If the list is empty, just put v into the head node's value.
- Otherwise, walk to the last node (the one whose
nextis None). - Make its
nextpoint to a new node holding v.
def appendi(self, v): # append, iterative
if self.isempty():
self.value = v
return
temp = self
while temp.next != None: # walk to the last node
temp = temp.next
temp.next = Node(v) # attach the new node
returnTrace: start with l = Node().
| Call | What happens | List |
|---|---|---|
l.appendi(1) | List empty → value becomes 1 | 1 |
l.appendi(2) | Walk to last (1), attach Node(2) | 1 → 2 |
l.appendi(3) | Walk 1 → 2, attach Node(3) | 1 → 2 → 3 |
Append takes O(n) time here, because we walk to the end each time. (A recursive version works the same way: if this is the last node, attach; otherwise ask self.next to append.)
Insert — add v at the start (the tricky one)
We want v to become the first value. The obvious idea — make a new node and point the head to it — doesn't work, because the variable l outside the method still points to the old head node. We cannot change where the head points.
The trick: keep the head node where it is, but swap the values:
- Create a new node holding v.
- Exchange the values of the head node and the new node. Now the head holds v, and the new node holds the old first value v₀.
- Re-link: new node points to what the head pointed to; head points to the new node.
def insert(self, v):
if self.isempty():
self.value = v
return
newnode = Node(v)
# Exchange values in self and newnode
(self.value, newnode.value) = (newnode.value, self.value)
# Switch links
(self.next, newnode.next) = (newnode, self.next)
returnTrace: l is 1 → 2 → 3. Call l.insert(0).
| Step | Head node | New node | Chain |
|---|---|---|---|
| Create new node | 1 (→ 2) | 0 | — |
| Swap values | 0 (→ 2) | 1 | — |
| Switch links | 0 → new node | 1 → 2 | 0 → 1 → 2 → 3 ✔ |
The head node is the same object as before (so l still works), but now it holds 0. Insert at the start takes O(1) time.

Delete — remove the first occurrence of v
Idea: scan the list and look one step ahead. If the next node holds v, bypass it: point the current node's next to the node after it.
Problem: we can't bypass the first node (the head can't move). Fix: copy the second node's value into the head, then bypass the second node.
def delete(self, v): # delete, recursive
if self.isempty():
return
if self.value == v:
self.value = None
if self.next != None:
self.value = self.next.value # copy the 2nd value into the head
self.next = self.next.next # bypass the 2nd node
return
else:
if self.next != None:
self.next.delete(v) # delete from the rest of the list
if self.next.value == None: # the next node became empty
self.next = None # → cut it off
returnTraces (all checked by running the code)
| List before | delete(v) | What happens | List after |
|---|---|---|---|
| 1 → 2 → 3 | delete(2) | head ≠ 2; recurse into node 2; it holds 2, so it copies 3 into itself and bypasses node 3 | 1 → 3 |
| 1 → 2 → 3 | delete(1) | head holds 1: copy 2 into the head, bypass node 2 | 2 → 3 |
| 1 → 2 → 3 | delete(3) | recurse to node 3; it holds 3 and has no next, so its value becomes None; node 2 sees this and cuts it off | 1 → 2 |
| 1 | delete(1) | head holds 1 and has no next → value becomes None | empty |
| 5 → 5 → 6 | delete(5) | only the first 5 is removed | 5 → 6 |
| 1 → 2 → 3 | delete(9) | 9 not found, nothing changes | 1 → 2 → 3 |

(Exercise from the lecture: write an iterative version of delete.)
Summary of operations
| Operation | Trick | Time |
|---|---|---|
isempty() | Empty list = node with value None | O(1) |
appendi(v) | Walk to the last node, attach a new node | O(n) |
insert(v) | Swap values with a new node, switch links (head can't move) | O(1) |
delete(v) | Look ahead and bypass; for the head, copy the next value in | O(n) |
In one line: A linked list is a chain of Node objects; append walks to the end, insert swaps values so the head stays put, and delete looks one step ahead to bypass the node.
Exam words: Node, value, next, empty list (value None), singleton, appendi, insert at head, swap values, switch links, delete first occurrence, look ahead, bypass.
Quick check
1. What does Node().isempty() return? And Node(7).isempty()?
True and False.2. l is 4 → 5. After l.insert(3) and then l.appendi(6), what is l?
Insert at start: 3 → 4 → 5. Append: 3 → 4 → 5 → 6.3. Why can't insert just create a new node and make it the head?
The variable holding the list still points to the old head node. A method can't change what that outside variable points to, so the new node would be lost. Swapping values keeps the same head node.4. l is 7 → 8 → 7. What is l after l.delete(7)?
Only the first 7 is removed: 8 → 7.5. What is the time complexity of inserting at the start? Of appending at the end?
Insert at start: O(1). Append at end: O(n) (walk to the last node).Linked List Insertion playground
Move one step at a time. The visual state and explanation update together.
Start with a chain of nodes. Each node points to the next one.
Why? A linked list stores connections instead of neighbouring array positions.
