Singly Linked List
What Is a Singly Linked List?
A singly linked list is a linear data structure that stores elements in nodes scattered across memory. Each node contains a value and a single pointer — next — that points to the next node in the chain. The last node's next is null, marking the end of the list.
Singly linked list: 10 → 20 → 30 → 40 → null ┌──────┬──────┐ ┌──────┬──────┐ ┌──────┬──────┐ ┌──────┬──────┐ │ 10 │ ●───┼──▶│ 20 │ ●───┼──▶│ 30 │ ●───┼──▶│ 40 │ null │ └──────┴──────┘ └──────┴──────┘ └──────┴──────┘ └──────┴──────┘ head tail ● = next pointer (address of the next node)
Why "singly"? Each node has only one pointer — forward only. You can move from head toward the tail, but not backward. This is the simplest and most memory-efficient variant.
Node and LinkedList Structure
Every singly linked list consists of two pieces:
- ›Node class — the building block: stores
dataand anextpointer - ›LinkedList class — the container: stores the
headreference and defines all operations
1// Node — the building block
2class Node {
3 int data; // Value stored in this node
4 Node next; // Pointer to the next node
5
6 Node(int data) {
7 this.data = data;
8 this.next = null; // New node has no next by default
9 }
10}
11
12// LinkedList — stores head and all operations
13class LinkedList {
14 Node head;
15
16 LinkedList() {
17 head = null; // Empty list — no nodes yet
18 }
19}Operation 1: Traverse — Visit Every Node
Traversal is the foundation of all other operations. Start at head, visit each node, follow next until null.
1// Traversing — print every node's data
2public void traverse() {
3 Node temp = head;
4
5 while (temp != null) {
6 System.out.print(temp.data + " → ");
7 temp = temp.next;
8 }
9
10 System.out.println("null");
11}Dry Run: Traverse [10 → 20 → 30 → null]
temp = head = Node(10) Step 1: temp=Node(10) → print 10, temp = temp.next = Node(20) Step 2: temp=Node(20) → print 20, temp = temp.next = Node(30) Step 3: temp=Node(30) → print 30, temp = temp.next = null Step 4: temp=null → loop ends Output: 10 → 20 → 30 → null Key rule: use temp != null (not temp.next != null) Using temp.next != null would skip the last node.
Operation 2: Length — Count All Nodes
Walk from head to null, counting each node visited.
1public int length() {
2 Node temp = head;
3 int count = 0;
4
5 while (temp != null) {
6 count++;
7 temp = temp.next;
8 }
9
10 return count;
11}Output (for list 10 → 20 → 30 → 40 → 50): 5
Operation 3: Search — Find an Element
Scan each node. Return true as soon as the target value is found. Return false after reaching the end without a match.
1public boolean findKey(int data) {
2 Node temp = head;
3
4 while (temp != null) {
5 if (temp.data == data) {
6 return true; // Found — stop immediately
7 }
8 temp = temp.next;
9 }
10
11 return false; // Not found
12}Output:
findKey(30) → true
findKey(99) → false
Operation 4: Insert at Beginning — O(1)
Create a new node, point its next to the current head, then make it the new head.
Before: head → 20 → 30 → null Insert 10 at beginning: Step 1: Create newNode(10), newNode.next = null Step 2: newNode.next = head (point new node to 20) Step 3: head = newNode (new node becomes the new head) After: head → 10 → 20 → 30 → null
1public void insertAtBeg(int data) {
2 Node newNode = new Node(data);
3
4 if (head == null) {
5 // Empty list — new node becomes the only node
6 head = newNode;
7 } else {
8 newNode.next = head; // Point new node to current head
9 head = newNode; // New node becomes the new head
10 }
11}Insert 50, 40, 30, 20, 10 at beginning: insertAtBeg(50) → 50 → null insertAtBeg(40) → 40 → 50 → null insertAtBeg(30) → 30 → 40 → 50 → null insertAtBeg(20) → 20 → 30 → 40 → 50 → null insertAtBeg(10) → 10 → 20 → 30 → 40 → 50 → null
Operation 5: Insert at End — O(n)
Traverse to the last node (the one whose next is null), then attach the new node there.
1public void insertAtEnd(int data) {
2 Node newNode = new Node(data);
3
4 if (head == null) {
5 // Empty list — new node is both head and tail
6 head = newNode;
7 } else {
8 Node temp = head;
9
10 // Walk until the last node
11 while (temp.next != null) {
12 temp = temp.next;
13 }
14
15 temp.next = newNode; // Attach new node after the last node
16 }
17}Dry Run: Insert 50 at End of [10 → 20 → 30 → null]
newNode = Node(50) temp = head = Node(10) Walk to last node: temp=Node(10): temp.next=Node(20) ≠ null → temp = Node(20) temp=Node(20): temp.next=Node(30) ≠ null → temp = Node(30) temp=Node(30): temp.next=null = null → STOP Attach: temp.next = newNode(50) Result: 10 → 20 → 30 → 50 → null Cost: O(n) — must traverse entire list to find the tail. Optimization: maintain a tail pointer for O(1) append.
Operation 6: Insert at Middle — After a Given Value
Find the node with value prev, then insert the new node immediately after it.
1public void insertAtMid(int prev, int data) {
2 Node newNode = new Node(data);
3 Node temp = head;
4
5 // Walk until we find the node with value `prev`
6 while (temp != null && temp.data != prev) {
7 temp = temp.next;
8 }
9
10 if (temp == null) {
11 System.out.println("Value " + prev + " not found in list.");
12 return;
13 }
14
15 // Insert after temp: temp → newNode → temp.next
16 newNode.next = temp.next;
17 temp.next = newNode;
18}Dry Run: Insert 25 After 20 in [10 → 20 → 30 → null]
newNode = Node(25) temp = head = Node(10) Find node with value 20: temp=Node(10): 10 ≠ 20 → temp = Node(20) temp=Node(20): 20 = 20 → STOP Insert after temp(20): newNode.next = temp.next = Node(30) temp.next = newNode Result: 10 → 20 → 25 → 30 → null Order matters: Step 1: newNode.next = temp.next (save 30 before losing it) Step 2: temp.next = newNode (redirect 20 to new node) Reversing the order would lose the reference to Node(30).
Operation 7: Delete from Beginning — O(1)
Move head forward to the second node. The first node is now unreferenced and will be garbage collected.
1public void deleteAtBeg() {
2 if (head == null) {
3 System.out.println("List is empty.");
4 return;
5 }
6 head = head.next; // Move head forward — old head is unreferenced
7}Before: head → 10 → 20 → 30 → null After deleteAtBeg(): head → 20 → 30 → null Cost: O(1) — just one pointer update. Always guard against empty list before deleting.
Operation 8: Delete from End — O(n)
Traverse to the second-to-last node, set its next to null.
1public void deleteAtEnd() {
2 if (head == null) {
3 System.out.println("List is empty.");
4 return;
5 }
6
7 // Only one node — list becomes empty
8 if (head.next == null) {
9 head = null;
10 return;
11 }
12
13 Node temp = head;
14 Node temp1 = null;
15
16 // Walk until temp is the last node
17 while (temp.next != null) {
18 temp1 = temp; // temp1 trails one step behind
19 temp = temp.next;
20 }
21
22 temp1.next = null; // Detach the last node
23}Dry Run: Delete from End of [10 → 20 → 30 → null]
temp = Node(10) temp1 = null Walk (temp1 always trails one step behind temp): temp=Node(10): temp.next=Node(20) ≠ null → temp1=Node(10), temp=Node(20) temp=Node(20): temp.next=Node(30) ≠ null → temp1=Node(20), temp=Node(30) temp=Node(30): temp.next=null = null → STOP Detach: temp1.next = null Node(20).next = null Result: 10 → 20 → null (Node(30) is now unreferenced) Why do we need temp1? We need to set the second-to-last node's next to null. Without temp1 tracking one step behind, we cannot go backward (singly linked — no prev pointer).
Operation 9: Delete from Middle — Remove by Value
Find the target node and the node before it, then bypass the target.
1public void deleteAtMid(int data) {
2 if (head == null) {
3 System.out.println("List is empty.");
4 return;
5 }
6
7 // Special case: deleting the head
8 if (head.data == data) {
9 head = head.next;
10 return;
11 }
12
13 Node temp = head;
14 Node temp1 = null;
15
16 // Find the node with the target value
17 while (temp != null && temp.data != data) {
18 temp1 = temp;
19 temp = temp.next;
20 }
21
22 if (temp == null) {
23 System.out.println("Value " + data + " not found.");
24 return;
25 }
26
27 // Bypass temp: temp1.next skips over temp to temp.next
28 temp1.next = temp.next;
29}Dry Run: Delete 30 from [10 → 20 → 30 → 40 → null]
temp = Node(10) temp1 = null Find node with data=30: temp=Node(10): 10 ≠ 30 → temp1=Node(10), temp=Node(20) temp=Node(20): 20 ≠ 30 → temp1=Node(20), temp=Node(30) temp=Node(30): 30 = 30 → STOP Bypass: temp1.next = temp.next Node(20).next = Node(30).next = Node(40) Result: 10 → 20 → 40 → null (Node(30) removed) The bypass pattern: prev.next = curr.next This skips `curr` — the node to delete — entirely.
Complete Working Example
1public class Main {
2
3 public static void main(String[] args) {
4 LinkedList obj = new LinkedList();
5
6 // Insert at beginning: builds 50 → 40 → 30 → 20 → 10 → null
7 obj.insertAtBeg(10);
8 obj.insertAtBeg(20);
9 obj.insertAtBeg(30);
10 obj.insertAtBeg(40);
11 obj.insertAtBeg(50);
12
13 System.out.println("After insertions:");
14 obj.traverse(); // 50 → 40 → 30 → 20 → 10 → null
15 System.out.println("Length: " + obj.length()); // 5
16
17 // Insert 25 after 20
18 obj.insertAtMid(20, 25);
19 obj.traverse(); // 50 → 40 → 30 → 20 → 25 → 10 → null
20
21 // Insert 99 at end
22 obj.insertAtEnd(99);
23 obj.traverse(); // 50 → 40 → 30 → 20 → 25 → 10 → 99 → null
24
25 // Delete from beginning
26 obj.deleteAtBeg();
27 obj.traverse(); // 40 → 30 → 20 → 25 → 10 → 99 → null
28
29 // Delete 30 from middle
30 obj.deleteAtMid(30);
31 obj.traverse(); // 40 → 20 → 25 → 10 → 99 → null
32
33 // Delete from end
34 obj.deleteAtEnd();
35 obj.traverse(); // 40 → 20 → 25 → 10 → null
36
37 // Search
38 System.out.println("Find 25: " + obj.findKey(25)); // true
39 System.out.println("Find 99: " + obj.findKey(99)); // false
40
41 System.out.println("Final length: " + obj.length()); // 4
42 }
43}Output:
After insertions:
50 → 40 → 30 → 20 → 10 → null
Length: 5
50 → 40 → 30 → 20 → 25 → 10 → null
50 → 40 → 30 → 20 → 25 → 10 → 99 → null
40 → 30 → 20 → 25 → 10 → 99 → null
40 → 20 → 25 → 10 → 99 → null
40 → 20 → 25 → 10 → null
Find 25: true
Find 99: false
Final length: 4
Operation Complexity Summary
| Operation | Time | Space | Notes |
|---|---|---|---|
| Traverse | O(n) | O(1) | Visit every node once |
| Length | O(n) | O(1) | Count every node |
| Search | O(n) | O(1) | Best case O(1) if found at head |
| Insert at beginning | O(1) | O(1) | Two pointer updates |
| Insert at end | O(n) | O(1) | Must walk to tail |
| Insert at middle | O(n) | O(1) | Walk to target position |
| Delete from beginning | O(1) | O(1) | One pointer update |
| Delete from end | O(n) | O(1) | Must walk to second-to-last |
| Delete from middle | O(n) | O(1) | Walk to target, bypass |
Common Mistakes Beginners Make
Using temp.next != null instead of temp != null in traversal. This skips the last node — the loop exits one step too early when temp.next becomes null, leaving the last node unvisited. Always use temp != null as the loop condition.
Swapping the order in mid-insert. When inserting after a node, always set newNode.next = temp.next BEFORE temp.next = newNode. Reversing this loses the reference to the rest of the list.
Not handling the empty list case. Before any delete operation, always check if (head == null). Calling head.next on a null head causes a NullPointerException.
Not handling the single-node case in deleteAtEnd. If the list has exactly one node, temp1 remains null when the loop ends. Calling temp1.next = null crashes. Always check head.next == null before entering the loop.
Not handling "delete head" in deleteAtMid. The two-pointer technique needs a prev node. If the target is the head, there is no prev — handle it as a special case by moving head = head.next.
Interview Questions
Q: What is the time complexity of inserting at the beginning vs the end of a singly linked list?
Inserting at the beginning is O(1) — create a new node, point it to the current head, update head to the new node. Two constant-time pointer updates regardless of list size. Inserting at the end is O(n) — you must traverse from head to the tail (the node whose next is null) before attaching the new node. With a maintained tail pointer, end insertion becomes O(1).
Q: Why does deleting the last node require O(n) in a singly linked list?
Because there is no prev pointer. To delete the last node, you must set the second-to-last node's next to null. But the only way to reach the second-to-last node is to traverse from the head until you find the node whose next.next is null — O(n). A doubly linked list solves this with a prev pointer, giving O(1) deletion with a maintained tail reference.
Q: How do you insert a node in the middle of a singly linked list without losing data?
Find the node after which you want to insert. Then: first set newNode.next = foundNode.next to save the reference to the rest of the list, then set foundNode.next = newNode to link the new node in. The order is critical — doing it in reverse would overwrite foundNode.next before saving it, losing all nodes after foundNode.
FAQs
Why does deleteAtMid need two pointers (temp and temp1)?
Because singly linked list nodes have no prev pointer — you cannot go backward. To delete a node, you need to redirect the previous node's next to skip the deleted node. temp1 trails one step behind temp, so when temp reaches the target, temp1 is already at the predecessor. Without temp1, once you reach the target node there is no way to reach the node before it.
What happens to the deleted node's memory?
In Java and Python, the garbage collector automatically reclaims memory when no references point to the node. In C++, you must explicitly call delete nodePointer after unlinking the node, or you create a memory leak. In JavaScript, the engine's garbage collector handles it automatically like Java.
Can I insert at middle using an index instead of a value?
Yes. Instead of while (temp.data != prev), use a counter: while (i < targetIndex - 1). Increment i and follow temp = temp.next until you reach the node just before the target index. This is O(n) either way — you are still traversing to find the position.
Quick Quiz
Question 1: You have a singly linked list 10 → 20 → 30 → null. What is the state after insertAtBeg(5)?
- ›A)
10 → 20 → 30 → 5 → null - ›B)
5 → 10 → 20 → 30 → null - ›C)
10 → 5 → 20 → 30 → null - ›D)
5 → null
Answer: B) 5 → 10 → 20 → 30 → null. Insert at beginning sets newNode.next = head (Node 10) then head = newNode (Node 5). The new node becomes the first node.
Question 2: What is the minimum number of pointer updates needed to delete a node from the middle of a singly linked list, given you already have prev pointing to the predecessor?
- ›A) 0
- ›B) 1
- ›C) 2
- ›D) n
Answer: B) 1. prev.next = curr.next — one assignment bypasses the target node. No other pointer updates are needed for the deletion itself.
Question 3: What is wrong with this traversal loop: while (temp.next != null)?
- ›A) It never terminates
- ›B) It skips the head node
- ›C) It skips the last node — the loop exits when
temp.nextis null, before visitingtemp - ›D) It throws a NullPointerException
Answer: C) It skips the last node. When temp points to the last node, temp.next is null — the condition is false and the loop exits without printing the last node's data. The correct condition is while (temp != null).
Question 4: In insertAtMid, you find the target node temp. What must happen FIRST before redirecting temp.next?
- ›A) Update
headtonewNode - ›B) Set
newNode.next = temp.nextto save the rest of the list - ›C) Set
temp.next = newNodeto link the new node - ›D) Delete
temp
Answer: B) Set newNode.next = temp.next first. This saves the reference to the node after the insertion point. If you set temp.next = newNode first, you lose the reference to temp.next (the rest of the list) before saving it — those nodes become unreachable.
Summary
A singly linked list chains nodes together using a single forward pointer. Each node stores its data and the address of the next node. The list is represented by its head — all operations start from there.
The nine core operations and their complexities:
- ›Traverse — O(n) — follow
nextuntil null; usetemp != null, nottemp.next != null - ›Length — O(n) — count during traversal
- ›Search — O(n) — scan for value, return true on first match
- ›Insert at beginning — O(1) — two pointer updates; always handle empty list
- ›Insert at end — O(n) — traverse to tail, attach new node
- ›Insert at middle — O(n) — find target, then:
newNode.next = temp.nextTHENtemp.next = newNode - ›Delete from beginning — O(1) —
head = head.next; guard empty list - ›Delete from end — O(n) — trail two pointers, set
prev.next = null; guard single-node case - ›Delete from middle — O(n) — trail two pointers, set
prev.next = curr.next; handle head deletion
In the next topic, you will explore the Doubly Linked List — adding a prev pointer to each node, enabling backward traversal and O(1) deletion at a known position.