Linked ListsLesson 2

Singly Linked List Operations

Insert at head, tail, and middle. Delete a node. Search for a value. Reverse the list. All operations step by step.

What Is It?

Knowing what a linked list is is not enough — you need to be able to do things with it. That means inserting new nodes, removing old ones, searching for values, and even reversing the whole chain.

Every operation comes down to one idea: find the right node, then update its next pointer.

Here is what we will cover, along with how fast each one is:

  • Insert at head — O(1) — instant, no matter how long the list
  • Insert at tail — O(n) — must walk to the end first
  • Insert at position — O(n) — must walk to the right spot
  • Delete head — O(1) — instant
  • Delete by value — O(n) — must search first
  • Search — O(n) — must check each node
  • Reverse — O(n) — must visit every node once

Analogy

Think of a singly linked list as a conga line at a party. Each person has their hands on the shoulders of the person in front of them (the next pointer). The person at the front is the head.

  • Insert at head: A new person steps to the very front and puts their hands on the old leader's shoulders. Instant — O(1).
  • Insert at tail: A new person must walk to the very back of the line. The longer the line, the longer the walk — O(n).
  • Delete head: The front person leaves. The person behind them becomes the new leader. Instant — O(1).
  • Delete by value: You must walk the line to find the person, then the person behind them reaches forward to the person ahead. O(n) to find them.
  • Reverse: Everyone turns around and puts their hands on the person who was behind them. This requires careful coordination.

How It Works

INSERT at Head — O(1)

This is the fastest insertion. You create a new node, point it at the current head, and update head.

pseudocode
1
2
3
4
5
function insertAtHead(list, value):
newNode = new Node(value)
newNode.next = list.head
list.head = newNode
list.size = list.size + 1

Before: Head -> [20] -> [30] -> null

pseudocode
1
2
3
Step 1: Create newNode [10]
Step 2: newNode.next = head => [10] -> [20] -> [30] -> null
Step 3: head = newNode

After: Head -> [10] -> [20] -> [30] -> null

Only 3 operations regardless of list size. That is why it is O(1).

INSERT at Tail — O(n)

You must walk to the last node, then attach the new node after it.

pseudocode
1
2
3
4
5
6
7
8
9
10
function insertAtTail(list, value):
newNode = new Node(value)
if list.head is null:
list.head = newNode
else:
current = list.head
while current.next is not null:
current = current.next
current.next = newNode
list.size = list.size + 1

Before: Head -> [10] -> [20] -> [30] -> null

pseudocode
1
2
Walk: current = [10], then [20], then [30] (stop current.next is null)
Attach: [30].next = newNode [40]

After: Head -> [10] -> [20] -> [30] -> [40] -> null

INSERT at Position — O(n)

To insert at position k (starting from 0), walk to the node at position k-1, then rewire.

pseudocode
1
2
3
4
5
6
7
8
9
10
11
12
13
function insertAtPosition(list, value, position):
if position equals 0:
insertAtHead(list, value)
return
newNode = new Node(value)
current = list.head
for i from 0 to position - 2:
if current is null:
error("Position out of bounds")
current = current.next
newNode.next = current.next
current.next = newNode
list.size = list.size + 1

Insert 25 at position 2:

Before: Head -> [10] -> [20] -> [30] -> [40] -> null

pseudocode
1
2
3
Walk to position 1: current = [20]
Step 1: newNode.next = current.next => [25] -> [30]
Step 2: current.next = newNode => [20] -> [25]

After: Head -> [10] -> [20] -> [25] -> [30] -> [40] -> null

Important: Step 1 must happen before Step 2. If you do Step 2 first, you lose the reference to [30] and everything after it — it is gone forever.

DELETE Head — O(1)

pseudocode
1
2
3
4
5
6
7
function deleteHead(list):
if list.head is null:
error("List is empty")
value = list.head.data
list.head = list.head.next
list.size = list.size - 1
return value

Before: Head -> [10] -> [20] -> [30] -> null

pseudocode
1
2
Step 1: Save value = 10
Step 2: head = head.next (head now points to [20])

After: Head -> [20] -> [30] -> null

DELETE by Value — O(n)

To delete a node, you need the node before it so you can make it skip over the target.

pseudocode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function deleteByValue(list, target):
if list.head is null:
return false
// Special case: target is at head
if list.head.data equals target:
list.head = list.head.next
list.size = list.size - 1
return true
// General case: find the node BEFORE the target
current = list.head
while current.next is not null:
if current.next.data equals target:
current.next = current.next.next
list.size = list.size - 1
return true
current = current.next
return false // not found

Delete value 20 from: Head -> [10] -> [20] -> [30] -> null

pseudocode
1
2
3
4
current = [10]
current.next.data = 20 => match!
current.next = current.next.next
[10].next = [30]

After: Head -> [10] -> [30] -> null

SEARCH — O(n)

pseudocode
1
2
3
4
5
6
7
8
9
function search(head, target):
current = head
index = 0
while current is not null:
if current.data equals target:
return index
current = current.next
index = index + 1
return -1 // not found

REVERSE — O(n) with Three Pointers

This is one of the most important linked list techniques. We use three pointers: prev, current, and next.

The idea: visit each node and flip its next pointer to point backward instead of forward.

pseudocode
1
2
3
4
5
6
7
8
9
function reverse(list):
prev = null
current = list.head
while current is not null:
next = current.next // save next node before we lose it
current.next = prev // flip the pointer backward
prev = current // move prev forward
current = next // move current forward
list.head = prev

Step-by-step on [10] -> [20] -> [30] -> null:

pseudocode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Start: prev=null, current=[10]
Step 1:
next = [20] // save [20] before we lose it
[10].next = null // flip: [10] now points backward (to nothing)
prev = [10]
current = [20]
Step 2:
next = [30] // save [30]
[20].next = [10] // flip: [20] now points back to [10]
prev = [20]
current = [30]
Step 3:
next = null // save null
[30].next = [20] // flip: [30] now points back to [20]
prev = [30]
current = null
Loop ends. head = prev = [30]

Result: Head -> [30] -> [20] -> [10] -> null

Examples

Example: Build a List and Reverse It

Build and Reverse a Linked ListStep-by-step list state after each operation
1 / 4
insertAtHead(30)
30
[0]

Example: Delete the Middle Element

Given [1] -> [2] -> [3] -> [4] -> [5], delete value 3:

pseudocode
1
2
3
4
5
6
current starts at [1]
current.next.data = 2, not 3. Move on.
current = [2]
current.next.data = 3, match!
current.next = current.next.next
[2].next = [4]

Result: [1] -> [2] -> [4] -> [5]

Common Mistakes

1. Forgetting the special case for head. When deleting or inserting at position 0, the head pointer itself must change. The general loop logic does not cover this — you need an explicit check at the top.

2. Rewiring pointers in the wrong order. During insertion, always set newNode.next BEFORE updating current.next. Doing it backwards severs the rest of the list.

3. Not saving `current.next` before reversing. In the reverse algorithm, once you flip current.next = prev, you can no longer reach the next node. You must save it in next first.

4. Off-by-one in position traversal. To insert at position k, stop at position k-1. Going one step too many puts the new node in the wrong place.

5. Not handling an empty list or single-node list. Operations like deleteHead and reverse need special handling when head is null or the list has only one node.

Best Practices

Return the value on delete. When deleting a node, return the data it contained. This is useful for stack and queue implementations built on linked lists.

Test edge cases first: empty list, single element, operation at the very beginning and very end. These are where most bugs hide.

Draw the pointers. Before coding any linked list operation, draw the before and after states with arrows. Identify exactly which pointers change and in what order.

Memorize the three-pointer reversal. It shows up constantly in interviews and is the foundation for more advanced problems like palindrome checking and reversing sublists.