Singly linked operations
The core operations on a singly linked list: insert at the head, tail and an index, delete the head, tail or a value, search, traverse and reverse, with the pointer updates drawn step by step.
Learning goals
- Insert and delete with the correct order of pointer updates
- Know which operations are O(1) and which need a walk
- Reverse a list in place
Prerequisites
Insert head
Inserting at the head creates a node, points it at the old head and makes it the new head: two assignments, O(1), independent of the list length. The order matters: set n.next = head before head = n, or the old list is lost.
Choose Insert at head in the animation.
1static Node insertHead(Node head, int v) {2Node n = new Node(v);3n.next = head; // first: point the new node at the old list4return n; // second: the new node becomes the head5}
Singly linked list: insert and delete
Interactive · edit the inputlinked list · 4 nodes
Insert at index. A node holds a value and a reference to the next node; the list is reached only through head.
1class Node { int val; Node next; Node(int v) { val = v; } }2static Node insertHead(Node head, int v) { Node n = new Node(v); n.next = head; return n; } // O(1)3static Node insertTail(Node head, int v) { // O(n) without a tail pointer4Node n = new Node(v);5if (head == null) return n;6Node cur = head; while (cur.next != null) cur = cur.next; // walk to the last node7cur.next = n; return head;8}9static Node insertAt(Node head, int index, int v) { // O(index)10if (index == 0) return insertHead(head, v);11Node prev = head; for (int i = 0; i < index - 1; i++) prev = prev.next; // stop at the node before the gap12Node n = new Node(v); n.next = prev.next; prev.next = n; return head; // link the new node first, then splice it in13}14static Node deleteHead(Node head) { return head == null ? null : head.next; } // O(1)15static Node deleteTail(Node head) {16if (head == null || head.next == null) return null;17Node cur = head; while (cur.next.next != null) cur = cur.next; // stop at the second-to-last node18cur.next = null; return head;19}20static Node deleteValue(Node head, int x) {21if (head == null) return null; if (head.val == x) return head.next;22Node cur = head; while (cur.next != null && cur.next.val != x) cur = cur.next;23if (cur.next != null) cur.next = cur.next.next; // bypass: the old node becomes garbage24return head;25}
Live state
length
4
head
10
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | length=4 head=10 | Insert at index. A node holds a value and a reference to the next node; the list is reached only through head. |
| 2 | L11 | length=4 head=10 i=0 | Walk 1 step to reach the node before position 2. |
| 3 | L11 | length=4 head=10 i=1 | prev = 20. |
| 4 | L12 | length=5 head=10 | Create node 25; it is not linked yet. |
| 5 | L12 | length=5 head=10 | n.next = prev.next: link the new node to the rest of the list FIRST, otherwise the rest would be lost. |
| 6 | L12 | length=5 head=10 | prev.next = n: splice the new node in. Only two references changed; nothing was shifted. |
| 7 | L12 | return=[10, 20, 25, 30, 40] | Inserted 25 at index 2: [10, 20, 25, 30, 40]. Cost is the walk, O(index). |
Insert tail
Appending to a list without a tail pointer walks to the last node (O(n)) and links the new node after it. An empty list is a special case: the new node becomes the head. With a stored tail reference the same operation is O(1).
Run Insert at tail and count the walking steps.
1static Node insertTail(Node head, int v) {2Node n = new Node(v);3if (head == null) return n;4Node cur = head;5while (cur.next != null) cur = cur.next; // walk to the last node6cur.next = n;7return head;8}
Singly linked list: insert and delete
Interactive · edit the inputlinked list · 4 nodes
Insert at index. A node holds a value and a reference to the next node; the list is reached only through head.
1class Node { int val; Node next; Node(int v) { val = v; } }2static Node insertHead(Node head, int v) { Node n = new Node(v); n.next = head; return n; } // O(1)3static Node insertTail(Node head, int v) { // O(n) without a tail pointer4Node n = new Node(v);5if (head == null) return n;6Node cur = head; while (cur.next != null) cur = cur.next; // walk to the last node7cur.next = n; return head;8}9static Node insertAt(Node head, int index, int v) { // O(index)10if (index == 0) return insertHead(head, v);11Node prev = head; for (int i = 0; i < index - 1; i++) prev = prev.next; // stop at the node before the gap12Node n = new Node(v); n.next = prev.next; prev.next = n; return head; // link the new node first, then splice it in13}14static Node deleteHead(Node head) { return head == null ? null : head.next; } // O(1)15static Node deleteTail(Node head) {16if (head == null || head.next == null) return null;17Node cur = head; while (cur.next.next != null) cur = cur.next; // stop at the second-to-last node18cur.next = null; return head;19}20static Node deleteValue(Node head, int x) {21if (head == null) return null; if (head.val == x) return head.next;22Node cur = head; while (cur.next != null && cur.next.val != x) cur = cur.next;23if (cur.next != null) cur.next = cur.next.next; // bypass: the old node becomes garbage24return head;25}
Live state
length
4
head
10
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | length=4 head=10 | Insert at index. A node holds a value and a reference to the next node; the list is reached only through head. |
| 2 | L11 | length=4 head=10 i=0 | Walk 1 step to reach the node before position 2. |
| 3 | L11 | length=4 head=10 i=1 | prev = 20. |
| 4 | L12 | length=5 head=10 | Create node 25; it is not linked yet. |
| 5 | L12 | length=5 head=10 | n.next = prev.next: link the new node to the rest of the list FIRST, otherwise the rest would be lost. |
| 6 | L12 | length=5 head=10 | prev.next = n: splice the new node in. Only two references changed; nothing was shifted. |
| 7 | L12 | return=[10, 20, 25, 30, 40] | Inserted 25 at index 2: [10, 20, 25, 30, 40]. Cost is the walk, O(index). |
Insert at index
To insert at position i, stop at the node before the gap (position i - 1), point the new node at that node's successor, and then point the node before it at the new node. Reversing those two writes would orphan the rest of the list. Cost O(i) for the walk, O(1) for the splice; index 0 is the head case.
Insert at index 2 is the default preset.
1static Node insertAt(Node head, int index, int v) {2if (index == 0) return insertHead(head, v);3Node prev = head;4for (int i = 0; i < index - 1; i++) prev = prev.next; // node before the gap5Node n = new Node(v);6n.next = prev.next; // 1) link the new node to the rest7prev.next = n; // 2) splice it in8return head;9}
Singly linked list: insert and delete
Interactive · edit the inputlinked list · 4 nodes
Insert at index. A node holds a value and a reference to the next node; the list is reached only through head.
1class Node { int val; Node next; Node(int v) { val = v; } }2static Node insertHead(Node head, int v) { Node n = new Node(v); n.next = head; return n; } // O(1)3static Node insertTail(Node head, int v) { // O(n) without a tail pointer4Node n = new Node(v);5if (head == null) return n;6Node cur = head; while (cur.next != null) cur = cur.next; // walk to the last node7cur.next = n; return head;8}9static Node insertAt(Node head, int index, int v) { // O(index)10if (index == 0) return insertHead(head, v);11Node prev = head; for (int i = 0; i < index - 1; i++) prev = prev.next; // stop at the node before the gap12Node n = new Node(v); n.next = prev.next; prev.next = n; return head; // link the new node first, then splice it in13}14static Node deleteHead(Node head) { return head == null ? null : head.next; } // O(1)15static Node deleteTail(Node head) {16if (head == null || head.next == null) return null;17Node cur = head; while (cur.next.next != null) cur = cur.next; // stop at the second-to-last node18cur.next = null; return head;19}20static Node deleteValue(Node head, int x) {21if (head == null) return null; if (head.val == x) return head.next;22Node cur = head; while (cur.next != null && cur.next.val != x) cur = cur.next;23if (cur.next != null) cur.next = cur.next.next; // bypass: the old node becomes garbage24return head;25}
Live state
length
4
head
10
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | length=4 head=10 | Insert at index. A node holds a value and a reference to the next node; the list is reached only through head. |
| 2 | L11 | length=4 head=10 i=0 | Walk 1 step to reach the node before position 2. |
| 3 | L11 | length=4 head=10 i=1 | prev = 20. |
| 4 | L12 | length=5 head=10 | Create node 25; it is not linked yet. |
| 5 | L12 | length=5 head=10 | n.next = prev.next: link the new node to the rest of the list FIRST, otherwise the rest would be lost. |
| 6 | L12 | length=5 head=10 | prev.next = n: splice the new node in. Only two references changed; nothing was shifted. |
| 7 | L12 | return=[10, 20, 25, 30, 40] | Inserted 25 at index 2: [10, 20, 25, 30, 40]. Cost is the walk, O(index). |
Delete head/tail
Deleting the head is one assignment: head = head.next, O(1). Deleting the tail needs the second-to-last node (the one whose next.next is null) and sets its next to null: O(n), because a singly linked list cannot step backwards. A one-element list becomes empty.
Try Delete tail and compare it with Delete head.
1static Node deleteHead(Node head) { return head == null ? null : head.next; }2static Node deleteTail(Node head) {3if (head == null || head.next == null) return null;4Node cur = head;5while (cur.next.next != null) cur = cur.next; // second-to-last node6cur.next = null;7return head;8}
Singly linked list: insert and delete
Interactive · edit the inputlinked list · 4 nodes
Insert at index. A node holds a value and a reference to the next node; the list is reached only through head.
1class Node { int val; Node next; Node(int v) { val = v; } }2static Node insertHead(Node head, int v) { Node n = new Node(v); n.next = head; return n; } // O(1)3static Node insertTail(Node head, int v) { // O(n) without a tail pointer4Node n = new Node(v);5if (head == null) return n;6Node cur = head; while (cur.next != null) cur = cur.next; // walk to the last node7cur.next = n; return head;8}9static Node insertAt(Node head, int index, int v) { // O(index)10if (index == 0) return insertHead(head, v);11Node prev = head; for (int i = 0; i < index - 1; i++) prev = prev.next; // stop at the node before the gap12Node n = new Node(v); n.next = prev.next; prev.next = n; return head; // link the new node first, then splice it in13}14static Node deleteHead(Node head) { return head == null ? null : head.next; } // O(1)15static Node deleteTail(Node head) {16if (head == null || head.next == null) return null;17Node cur = head; while (cur.next.next != null) cur = cur.next; // stop at the second-to-last node18cur.next = null; return head;19}20static Node deleteValue(Node head, int x) {21if (head == null) return null; if (head.val == x) return head.next;22Node cur = head; while (cur.next != null && cur.next.val != x) cur = cur.next;23if (cur.next != null) cur.next = cur.next.next; // bypass: the old node becomes garbage24return head;25}
Live state
length
4
head
10
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | length=4 head=10 | Insert at index. A node holds a value and a reference to the next node; the list is reached only through head. |
| 2 | L11 | length=4 head=10 i=0 | Walk 1 step to reach the node before position 2. |
| 3 | L11 | length=4 head=10 i=1 | prev = 20. |
| 4 | L12 | length=5 head=10 | Create node 25; it is not linked yet. |
| 5 | L12 | length=5 head=10 | n.next = prev.next: link the new node to the rest of the list FIRST, otherwise the rest would be lost. |
| 6 | L12 | length=5 head=10 | prev.next = n: splice the new node in. Only two references changed; nothing was shifted. |
| 7 | L12 | return=[10, 20, 25, 30, 40] | Inserted 25 at index 2: [10, 20, 25, 30, 40]. Cost is the walk, O(index). |
Delete by value
To delete the first node holding x, walk with a reference to the node before the candidate: when cur.next.val == x, set cur.next = cur.next.next to bypass it. The head needs a separate check, because nothing precedes it. A dummy node in front of the head removes that special case. O(n).
Try Delete first node with value and the not-found preset.
1static Node deleteValue(Node head, int x) {2if (head == null) return null;3if (head.val == x) return head.next;4Node cur = head;5while (cur.next != null && cur.next.val != x) cur = cur.next;6if (cur.next != null) cur.next = cur.next.next; // bypass the node: it becomes garbage7return head;8}
Singly linked list: insert and delete
Interactive · edit the inputlinked list · 4 nodes
Insert at index. A node holds a value and a reference to the next node; the list is reached only through head.
1class Node { int val; Node next; Node(int v) { val = v; } }2static Node insertHead(Node head, int v) { Node n = new Node(v); n.next = head; return n; } // O(1)3static Node insertTail(Node head, int v) { // O(n) without a tail pointer4Node n = new Node(v);5if (head == null) return n;6Node cur = head; while (cur.next != null) cur = cur.next; // walk to the last node7cur.next = n; return head;8}9static Node insertAt(Node head, int index, int v) { // O(index)10if (index == 0) return insertHead(head, v);11Node prev = head; for (int i = 0; i < index - 1; i++) prev = prev.next; // stop at the node before the gap12Node n = new Node(v); n.next = prev.next; prev.next = n; return head; // link the new node first, then splice it in13}14static Node deleteHead(Node head) { return head == null ? null : head.next; } // O(1)15static Node deleteTail(Node head) {16if (head == null || head.next == null) return null;17Node cur = head; while (cur.next.next != null) cur = cur.next; // stop at the second-to-last node18cur.next = null; return head;19}20static Node deleteValue(Node head, int x) {21if (head == null) return null; if (head.val == x) return head.next;22Node cur = head; while (cur.next != null && cur.next.val != x) cur = cur.next;23if (cur.next != null) cur.next = cur.next.next; // bypass: the old node becomes garbage24return head;25}
Live state
length
4
head
10
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | length=4 head=10 | Insert at index. A node holds a value and a reference to the next node; the list is reached only through head. |
| 2 | L11 | length=4 head=10 i=0 | Walk 1 step to reach the node before position 2. |
| 3 | L11 | length=4 head=10 i=1 | prev = 20. |
| 4 | L12 | length=5 head=10 | Create node 25; it is not linked yet. |
| 5 | L12 | length=5 head=10 | n.next = prev.next: link the new node to the rest of the list FIRST, otherwise the rest would be lost. |
| 6 | L12 | length=5 head=10 | prev.next = n: splice the new node in. Only two references changed; nothing was shifted. |
| 7 | L12 | return=[10, 20, 25, 30, 40] | Inserted 25 at index 2: [10, 20, 25, 30, 40]. Cost is the walk, O(index). |
Search
Searching a linked list is a linear scan: follow next until the value is found or the list ends, O(n) with no random access and no binary search even if the list is sorted. Return the node, its index or a boolean as needed.
Because nodes are scattered in memory, scans are also slower in practice than array scans.
1static int indexOf(Node head, int x) {2int i = 0;3for (Node cur = head; cur != null; cur = cur.next, i++) if (cur.val == x) return i;4return -1;5}6static boolean contains(Node head, int x) { return indexOf(head, x) >= 0; }
Traverse
Traversal visits every node from the head with a single cur reference: for (Node cur = head; cur != null; cur = cur.next). It is O(n) time and O(1) space. Do not modify head itself while traversing, or the list is lost; use a separate cursor. Recursion also works but uses O(n) stack.
The array animation shows the cost difference when reaching element k.
1static void print(Node head) {2StringBuilder sb = new StringBuilder();3for (Node cur = head; cur != null; cur = cur.next) sb.append(cur.val).append(cur.next != null ? " -> " : "");4System.out.println(sb);5}6static void printRecursive(Node n) { if (n == null) return; System.out.print(n.val + " "); printRecursive(n.next); }
Reading element k: array vs linked list
Interactive · edit the inputarray memory (value, address)
The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000.
1static int getFromArray(int[] arr, int k) {2return arr[k]; // address = base + k * 4: one memory read3}45static int getFromList(Node head, int k) {6Node cur = head;7for (int i = 0; i < k; i++) {8cur = cur.next; // pointer chase: one read per hop9}10return cur.value;11}
Live state
k
5
base
1000
Execution trace (dry run table)Show 3 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | k=5 base=1000 | The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000. |
| 2 | L2 | k=5 address=1020 | Address = 1000 + 5 × 4 = 1020. One multiplication and one addition give the location, no matter how large k is. |
| 3 | L2 | return=8 | return 8 with a single memory read: O(1) random access. |
Reverse
Reversing flips every next pointer using three references: save next, point curr.next at prev, then advance both. The values never move. After the loop prev is the new head. O(n) time and O(1) space.
Step through the pointer flips; the arrows turn around one at a time.
1static Node reverse(Node head) {2Node prev = null, curr = head;3while (curr != null) {4Node next = curr.next; // save the rest5curr.next = prev; // flip6prev = curr; curr = next; // advance7}8return prev;9}
Reversing a linked list by flipping pointers
Interactive · edit the inputlinked list · 5 nodes
Reversal flips every next pointer. No values move: only the arrows change, in O(n) time and O(1) space.
1static Node reverse(Node head) {2Node prev = null, curr = head;3while (curr != null) {4Node next = curr.next; // save the rest of the list5curr.next = prev; // flip the pointer6prev = curr; curr = next; // step forward7}8return prev; // prev is the new head9}10static Node reverseBetween(Node head, int left, int right) { // 1-based positions11Node dummy = new Node(0); dummy.next = head; Node pre = dummy;12for (int i = 1; i < left; i++) pre = pre.next; // node before the segment13Node curr = pre.next;14for (int i = 0; i < right - left; i++) {15Node next = curr.next;16curr.next = next.next; next.next = pre.next; pre.next = next; // move next to the front of the segment17}18return dummy.next;19}20static Node reverseKGroup(Node head, int k) { // groups shorter than k stay as they are21Node dummy = new Node(0); dummy.next = head; Node groupPrev = dummy;22while (true) {23Node kth = groupPrev; for (int i = 0; i < k && kth != null; i++) kth = kth.next;24if (kth == null) break; // fewer than k nodes left25Node groupStart = groupPrev.next, groupNext = kth.next, prev = groupNext, curr = groupStart;26while (curr != groupNext) { Node next = curr.next; curr.next = prev; prev = curr; curr = next; }27groupPrev.next = kth; groupPrev = groupStart;28}29return dummy.next;30}
Live state
head
1
Execution trace (dry run table)Show 17 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | head=1 | Reversal flips every next pointer. No values move: only the arrows change, in O(n) time and O(1) space. |
| 2 | L4 | head=1 | Save next = 2 before changing curr.next, or the rest of the list would be lost. |
| 3 | L5 | head=1 | curr.next = prev: node 1 now points to null (it becomes the tail). |
| 4 | L6 | head=1 | Step both pointers forward. |
| 5 | L4 | head=1 | Save next = 3 before changing curr.next, or the rest of the list would be lost. |
| 6 | L5 | head=1 | curr.next = prev: node 2 now points back at 1. |
| 7 | L6 | head=1 | Step both pointers forward. |
| 8 | L4 | head=1 | Save next = 4 before changing curr.next, or the rest of the list would be lost. |
| 9 | L5 | head=1 | curr.next = prev: node 3 now points back at 2. |
| 10 | L6 | head=1 | Step both pointers forward. |
| 11 | L4 | head=1 | Save next = 5 before changing curr.next, or the rest of the list would be lost. |
| 12 | L5 | head=1 | curr.next = prev: node 4 now points back at 3. |
| 13 | L6 | head=1 | Step both pointers forward. |
| 14 | L4 | head=1 | Save next = null before changing curr.next, or the rest of the list would be lost. |
| 15 | L5 | head=1 | curr.next = prev: node 5 now points back at 4. |
| 16 | L6 | head=1 | Step both pointers forward. |
| 17 | L8 | head=5 return=[5, 4, 3, 2, 1] | Return prev as the new head: [5, 4, 3, 2, 1]. |
Practice ladder
Now try it yourself
Easy
- Insert 5 at the head of 10 → 20
- Delete the head of a one-node list
Medium
- Delete the first node with value 30 from 10 → 20 → 30 → 40
- Reverse 1 → 2 → 3 by hand writing prev, curr, next at each step
Advanced
- Delete every node with a given value in one pass using a dummy node
Key takeaways
- Link the new node to the rest first, then splice it in.
- Head operations are O(1); anything that needs the previous node walks O(n).
- Reverse with three pointers: save next, flip, advance.