What Is Linked List Insertion?
Linked list insertion is the process of adding a new node to an existing linked list. Unlike an array, a linked list does not normally require the existing elements to be shifted when a new node is inserted. Instead, insertion is performed by creating a node and updating one or more references.
The exact steps depend on where the new node needs to be inserted. Common insertion operations include inserting at the beginning, inserting at the end, and inserting at a specific position or after a known node.
How Insertion Works
A singly linked list node generally contains two parts: the data and a reference to the next node.
const newNode = {
data: 25,
next: null
};
To insert this node, the list must be connected correctly. The new node should point to the appropriate next node, and the previous node should point to the new node when the insertion happens somewhere in the middle.
Inserting at the Beginning
Inserting at the beginning is one of the simplest linked list operations. The new node
becomes the new head, while its next reference points to the previous head.
const newNode = {
data: 10,
next: head
};
head = newNode;
Before insertion, the list may look like:
20 → 30 → 40 → null
After inserting 10 at the beginning:
10 → 20 → 30 → 40 → null
Only the new node's reference and the head reference need to be changed. Therefore,
insertion at the beginning takes constant time, O(1).
Inserting at the End
To insert a node at the end of a singly linked list, the list must be traversed until the
last node is found. The last node's next reference is then updated to point
to the new node.
const newNode = {
data: 50,
next: null
};
let current = head;
while (current.next !== null) {
current = current.next;
}
current.next = newNode;
If the list is:
10 → 20 → 30 → null
inserting 40 at the end produces:
10 → 20 → 30 → 40 → null
Without a tail reference, finding the final node requires traversal, so the operation takes
O(n) time. If the linked list maintains a tail pointer, insertion at the end
can be performed in O(1) time.
Inserting After a Specific Node
A common linked list operation is inserting a new node immediately after a known node. The important part is to preserve the existing connection before changing it.
Suppose the list contains:
10 → 20 → 40 → null
To insert 30 after 20, first make the new node point to
40. Then make 20 point to the new node.
newNode.next = current.next;
current.next = newNode;
The resulting list is:
10 → 20 → 30 → 40 → null
Important: Set newNode.next before replacing
current.next. Otherwise, the reference to the remaining part of the list
can be lost.
Inserting at a Specific Position
Inserting at a specific position usually requires traversing the list until the node before the desired position is reached.
For example, to insert 25 at position 2 in:
10 → 20 → 30 → 40 → null
the result should be:
10 → 20 → 25 → 30 → 40 → null
The node at position 1 is the previous node. Its reference must be redirected
to the new node, while the new node points to the original next node.
Complete JavaScript Implementation
The following implementation provides insertion at the beginning, end, and a specified position.
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
insertAtBeginning(data) {
const newNode = new Node(data);
newNode.next = this.head;
this.head = newNode;
if (this.tail === null) {
this.tail = newNode;
}
this.size++;
}
insertAtEnd(data) {
const newNode = new Node(data);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.size++;
}
insertAtPosition(data, position) {
if (position < 0 || position > this.size) {
throw new Error('Invalid position');
}
if (position === 0) {
this.insertAtBeginning(data);
return;
}
if (position === this.size) {
this.insertAtEnd(data);
return;
}
const newNode = new Node(data);
let current = this.head;
for (let i = 0; i < position - 1; i++) {
current = current.next;
}
newNode.next = current.next;
current.next = newNode;
this.size++;
}
print() {
const values = [];
let current = this.head;
while (current !== null) {
values.push(current.data);
current = current.next;
}
console.log(values.join(' → '));
}
}
const list = new LinkedList();
list.insertAtEnd(10);
list.insertAtEnd(20);
list.insertAtEnd(40);
list.insertAtPosition(30, 2);
list.insertAtBeginning(5);
list.print();
The output is:
5 → 10 → 20 → 30 → 40
Insertion Using a Node Reference
If the node where insertion should happen is already known, no search is required. This is an important distinction when analyzing linked list performance.
function insertAfter(node, data) {
if (node === null) {
return;
}
const newNode = new Node(data);
newNode.next = node.next;
node.next = newNode;
}
The pointer changes themselves take O(1) time because only a constant number
of references are modified.
Insertion in an Empty Linked List
An empty list requires special handling because there is no existing head or tail.
When the first node is inserted, both head and tail should refer
to that node when the implementation maintains a tail reference.
const newNode = new Node(10);
head = newNode;
tail = newNode;
After this operation:
head
↓
10 → null
↑
tail
Insertion Complexity
| Insertion Operation | Time Complexity | Reason |
|---|---|---|
| Beginning | O(1) | Only head references are changed |
| End with tail | O(1) | Tail directly identifies the last node |
| End without tail | O(n) | List must be traversed to find the last node |
| After known node | O(1) | The insertion location is already known |
| At position | O(n) | Traversal may be required to reach the position |
Linked List Insertion vs Array Insertion
Linked lists and arrays handle insertion differently. An array usually stores elements in contiguous positions, so inserting an element in the middle can require existing elements to be shifted. A linked list changes references instead.
| Factor | Linked List | Array |
|---|---|---|
| Beginning insertion | O(1) | Usually O(n) |
| Middle insertion | O(1) after reaching the node | Usually O(n) |
| Random access | O(n) | O(1) |
| Memory overhead | Extra references per node | Generally lower per-element overhead |
Common Insertion Mistakes
- Changing the previous node's reference before saving the original next node.
- Forgetting to update the head when inserting at position zero.
- Forgetting to update the tail when inserting at the end.
- Not handling insertion into an empty list.
- Allowing invalid positions such as negative indexes or positions beyond the list size.
- Creating a node but failing to connect it to the remaining list.
A Useful Way to Think About Insertion
The most important idea behind linked list insertion is that nodes themselves do not need to move. The links between nodes are changed.
Before:
A → B → C
Insert X after B:
B → X
X → C
Result:
A → B → X → C
Once this reference-changing pattern becomes familiar, insertion in singly linked lists becomes much easier to implement and reason about.
Linked List Insertion in Interviews
Linked list insertion is commonly used to test pointer manipulation and edge-case handling. Interview problems may ask you to insert at a position, insert after a given node, maintain a tail pointer, or build a list while repeatedly adding elements.
When solving an insertion problem, first identify the insertion location, determine which references currently connect the surrounding nodes, and then update those references in the correct order.
Key Takeaways
- Linked list insertion adds a new node by changing references between nodes.
- Insertion at the beginning takes O(1) time.
- Insertion at the end is O(1) when a tail reference is maintained.
- Insertion at a position can require O(n) traversal.
- Inserting after a known node takes O(1) time in a singly linked list.
- The original next reference should be preserved before changing links.
- Empty lists, head updates, tail updates, and invalid positions require careful handling.