In-place Linked List Manipulation
Reverse, merge and splice lists by rewiring next pointers with prev/curr/next, using a dummy head to remove edge cases.
- O(n)Time: each node is rewired at most once
- O(1)Extra space; copying to an array is O(n)
- 3 pointersprev, curr and next for an in-place reversal
Show what to say
“To reverse, merge, reorder or remove nodes in place, I rewire next pointers instead of copying values: save curr.next, flip, advance. I put a dummy node in front when the head can change, and fix the two end links after a splice. It is O(n) time and O(1) space.”
Trade-offs
- Iterative vs recursive reversal: same O(n) time, but recursion holds one frame per node and can overflow on lists of tens of thousands.
- Dummy head vs special-casing the head: one extra node gives the head a predecessor, so edits need fewer branches and fewer bugs.
- Reverse the back half vs copy values to an array: O(1) against O(n) extra space, but cut first or you create a cycle.
Bring it up before they ask
Save curr.next before you overwrite it, return prev or dummy.next rather than the old head, and test lists of length 0, 1 and 2.
Linked list problems are solved by rewiring next pointers. Save curr.next before you overwrite it, and put a dummy node in front when the head can change. O(n) time, O(1) space.
When to reach for it
Tap the signals you can see in the problem.
RememberReverseMergereorderremoveback read backwards
- Reverse in place
Pointers
prev,curr,nextUse it whenevery arrow turns around
- Dummy head
Pointers
dummy, plustailorprevUse it whenthe head may change
- Reverse a sublist and splice
Pointers
before,first, plus the trioUse it whenonly part of the list flips
- Split, reverse, weave
Pointers
slow/fast, the trio,a/bUse it whenthe back half is needed backwards
- Fixed gap
Pointers
fastn + 1 ahead ofslowUse it whenyou count from the end
The core idea: save the link before you cut it
Picture a train where each car is coupled only to the car behind it.
Read more · 61 words
Uncouple a car before you note who was behind it, and the rest of the train is lost. So: save the link, overwrite it, advance. Three pointers do it: prev (the flipped part), curr (the car being flipped) and next (the saved rest):
prev starts as null: the old head becomes the tail. At the end, prev is the new head.
Reverse a list: save, flip, advance
- prev
- null
- curr
- 1
prev starts as null: the first node will become the tail, and the tail must point to null. curr starts on the head.
function reverseList(head) {
let prev = null, curr = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}All steps as text
- prev starts as null: the first node will become the tail, and the tail must point to null. curr starts on the head.
- Save curr.next (2) in next before touching anything. Once the arrow is flipped, next is the only way to reach the rest of the list.
- Point 1 at prev, which is null: 1 is now the tail of the reversed part.
- Move prev to 1 (the reversed part now starts there) and curr to the saved next, 2.
- Save curr.next (3) in next before touching anything. Once the arrow is flipped, next is the only way to reach the rest of the list.
- Point 2 back at prev (1). The arrow 2 → 3 is gone, but next still holds the rest.
- Move prev to 2 (the reversed part now starts there) and curr to the saved next, 3.
- Save curr.next (4) in next before touching anything. Once the arrow is flipped, next is the only way to reach the rest of the list.
- Point 3 back at prev (2). The arrow 3 → 4 is gone, but next still holds the rest.
- Move prev to 3 (the reversed part now starts there) and curr to the saved next, 4.
- Save curr.next (5) in next before touching anything. Once the arrow is flipped, next is the only way to reach the rest of the list.
- Point 4 back at prev (3). The arrow 4 → 5 is gone, but next still holds the rest.
- Move prev to 4 (the reversed part now starts there) and curr to the saved next, 5.
- Save curr.next: it is null, because curr is the last node.
- Point 5 back at prev (4). Its old arrow went to null, so nothing is lost.
- Move prev to 5 and curr to the saved next, which is null: every arrow has been flipped.
- curr is null, so the loop ends. prev is on 5, the old tail, which is the new head.
Play with it
Press the four assignments in order to reverse the train. Then flip before you save and watch the rest derail.
Read more · 58 words
Draw before and after
Circle every arrow that changes.
Name a pointer for every node you touch
prev,curr,next.Order the assignments so nothing is lost
Save
x.nextbefore you overwrite it.Put a dummy in front when the head can change
Return
dummy.next, nothead.Test the tiny lists
Empty, one node, two nodes.
Templates
Template 1: reverse a whole list
This is Reverse Linked ListEasy, the loop inside every template below.
// Template 1: reverse a list in place. prev heads the part that is already reversed; curr is the next node to flip.
// Save curr.next BEFORE overwriting it, or the rest of the list is lost.
export function reverseList(head) {
let prev = null;
let curr = head;
while (curr) {
const next = curr.next; // 1. save the rest
curr.next = prev; // 2. flip the arrow
prev = curr; // 3. move both pointers forward
curr = next;
}
return prev; // the old tail is the new head
}Template 2: a dummy head
If the head can go, what you return changes.
Read more · 52 words
A dummy node in front gives the head a predecessor, so there is no special case. Stop one node before the target and return dummy.next.
mergeSorted is Merge Two Sorted ListsEasy. For Remove Nth Node From End of ListMedium, start fast n + 1 steps ahead, then move both until fast is null: slow stops just before the node to remove.
import { ListNode } from './list.js';
// Template 2: a dummy node in front of the head, so the head can be removed or replaced like any other node.
export function removeAll(head, value) {
const dummy = new ListNode(0, head);
let prev = dummy;
while (prev.next) {
if (prev.next.val === value) prev.next = prev.next.next; // unlink; don't move: the new prev.next is unchecked
else prev = prev.next;
}
return dummy.next; // may not be the old head any more
}
// Building a list: with a dummy and a tail pointer, appending never needs a special case for the first node.
export function mergeSorted(a, b) {
const dummy = new ListNode();
let tail = dummy;
while (a && b) {
if (a.val <= b.val) {
tail.next = a;
a = a.next;
} else {
tail.next = b;
b = b.next;
}
tail = tail.next;
}
tail.next = a ?? b; // the rest of the other list is already sorted
return dummy.next;
}Template 3: reverse a sublist and splice it back
Stand on before, the node in front of the part, and reverse k nodes.
Read more · 26 words
Two links are now wrong. Fix them: before.next = prev and first.next = curr:
Repeat it for Reverse Linked List IIMedium (one range) or Reverse Nodes in k-GroupHard (while k nodes remain).
Reverse a sublist and splice it back
- k
- 3
- first
- 3
D is a dummy node in front of the head. before is node 2, found by walking 2 steps from D. first = before.next = 3: after the reversal it will be the segment's last node.
function reverseAfter(before, k) {
const first = before.next;
let prev = null, curr = first;
for (let i = 0; i < k; i++) {
const next = curr.next;
curr.next = prev;
prev = curr; curr = next;
}
before.next = prev;
first.next = curr;
return first;
}All steps as text
- D is a dummy node in front of the head. before is node 2, found by walking 2 steps from D. first = before.next = 3: after the reversal it will be the segment's last node.
- Save next = 4 before flipping 3.
- Point 3 at null for now. It is the segment's future tail; the last splice step will connect it to the rest of the list.
- Move prev to 3 and curr to 4. 1 of 3 nodes reversed.
- Save next = 5 before flipping 4.
- Point 4 back at 3.
- Move prev to 4 and curr to 5. 2 of 3 nodes reversed.
- Save next = 6 before flipping 5.
- Point 5 back at 4.
- Move prev to 5 and curr to 6. 3 of 3 nodes reversed.
- The segment now reads 5 → 4 → 3, but before still pointed at 3, its old first node. Set before.next = prev, so 2 → 5.
- 3, now the segment's last node, still points at null. Set first.next = curr (6) to reattach the rest of the list.
- Return first (3): it is the node in front of whatever comes next, so the same call can reverse the next group. The list from D.next reads 1 → 2 → 5 → 4 → 3 → 6.
import { ListNode } from './list.js';
// Template 3: reverse the k nodes after `before` (they must exist) and splice them back into place.
// Returns the segment's new last node, which is the `before` of whatever comes next.
export function reverseAfter(before, k) {
const first = before.next; // will become the segment's last node
let prev = null;
let curr = first;
for (let i = 0; i < k; i++) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
before.next = prev; // before → the segment's new first node
first.next = curr; // the segment's new last node → the rest of the list
return first;
}
// Example: swap every two adjacent nodes, which is "reverse in groups of 2".
export function swapPairs(head) {
const dummy = new ListNode(0, head);
let before = dummy;
while (before.next && before.next.next) before = reverseAfter(before, 2);
return dummy.next;
}Template 4: split, reverse, weave
A list can't walk backwards, so reverse the back half:
Read more · 44 words
- Find the middle with fast and slow pointers.
- Cut there (
slow.next = null) and reverse the back half. - Walk both halves together: compare or weave.
Save both successors, then link a → b → aNext:
fold is Reorder ListMedium. Compare instead of weaving to check a Palindrome Linked ListEasy.
Weave the front half with the reversed back half
- a
- 1
- b
- 6
The list 1 → … → 6 was cut after its middle (3) and the back half reversed: a = 1 → 2 → 3 and b = 6 → 5 → 4. Nodes are drawn in their final order, so the curved arrows are the ones still to fix.
function weave(a, b) {
const head = a;
while (a && b) {
const aNext = a.next, bNext = b.next;
a.next = b;
b.next = aNext ?? bNext;
a = aNext; b = bNext;
}
return head;
}All steps as text
- The list 1 → … → 6 was cut after its middle (3) and the back half reversed: a = 1 → 2 → 3 and b = 6 → 5 → 4. Nodes are drawn in their final order, so the curved arrows are the ones still to fix.
- Save both successors first: aNext = 2 and bNext = 5. The next two lines overwrite a.next and b.next.
- a.next = b: 1 now points at 6.
- b.next = aNext: 6 points at 2, so the front half continues after it.
- Move a to 2 and b to 5.
- Save both successors first: aNext = 3 and bNext = 4. The next two lines overwrite a.next and b.next.
- a.next = b: 2 now points at 5.
- b.next = aNext: 5 points at 3, so the front half continues after it.
- Move a to 3 and b to 4.
- Save both successors first: aNext = null and bNext = null. The next two lines overwrite a.next and b.next.
- a.next = b: 3 now points at 4.
- aNext is null, so b keeps its own rest: 4 points at null.
- Both halves are used up.
- Every arrow is straight: the list from the head reads 1 → 6 → 2 → 5 → 3 → 4, first, last, second, second-to-last, and so on. No node was created or copied.
import { reverseList } from './reverse.js';
// Template 4: interleave two lists in place, a1 → b1 → a2 → b2 → …
// Save BOTH successors before rewiring; whichever list is longer keeps its leftover nodes at the end.
export function weave(a, b) {
const head = a;
while (a && b) {
const aNext = a.next;
const bNext = b.next;
a.next = b;
b.next = aNext ?? bNext; // if a has run out, b keeps the rest of its own list
a = aNext;
b = bNext;
}
return head;
}
// The combo: L0 → Ln → L1 → Ln-1 → … in place. Find the middle, cut, reverse the back half, weave.
export function fold(head) {
if (!head) return head;
let slow = head;
let fast = head;
while (fast.next && fast.next.next) { // slow ends on the last node of the front half
slow = slow.next;
fast = fast.next.next;
}
const back = reverseList(slow.next);
slow.next = null; // cut, or the front half still runs into the back half
return weave(head, back);
}Try it
// Edit the inputs and press Run. fromArray builds a list and toArray reads it back.
console.log(toArray(reverseList(fromArray([1, 2, 3, 4, 5])))); // [5, 4, 3, 2, 1]
console.log(toArray(removeAll(fromArray([6, 1, 6, 6, 2, 6]), 6))); // [1, 2]
console.log(toArray(mergeSorted(fromArray([1, 4, 9]), fromArray([2, 3, 10])))); // [1, 2, 3, 4, 9, 10]
const dummy = new ListNode(0, fromArray([1, 2, 3, 4, 5, 6]));
reverseAfter(dummy.next.next, 3); // the 3 nodes after node 2
console.log(toArray(dummy.next)); // [1, 2, 5, 4, 3, 6]
console.log(toArray(swapPairs(fromArray([1, 2, 3, 4, 5])))); // [2, 1, 4, 3, 5]
console.log(toArray(fold(fromArray([1, 2, 3, 4, 5, 6])))); // [1, 6, 2, 5, 3, 4]Complexity and JavaScript you'll use
Complexity
Read more · 114 words
| Technique | Time | Extra space |
|---|---|---|
| Reverse in place | O(n) | O(1) (recursive: O(n) call stack) |
| Dummy head: merge, remove | O(n + m) / O(n) | O(1): one dummy node |
| Reverse a sublist, or every group of k | O(n): each node reversed at most once | O(1) |
| Split, reverse, weave | O(n): three linear passes | O(1) |
| Copy values into an array instead | O(n) | O(n) |
JavaScript you'll use
JavaScript refresher: Linked list and tree nodes Destructuring, spread and restThe right side is evaluated first, but order on the left still matters.
const dummy = new ListNode(0, head); // a node in front of the head
const next = curr.next; // save before overwriting
curr.next = prev; // flip
[curr.next, prev, curr] = [prev, curr, curr.next]; // all three steps at once
return dummy.next; // the real head, even if it changedInterview questions
Iterative or recursive reversal?
Iterative: O(1) space. Recursion can overflow on long lists.
Why use a dummy node?
The head gets a predecessor, so editing it needs no special case.
How do you rotate a list right by k?
Join the tail to the head, walk n - k % n - 1 steps to the new tail, and cut.
Add two numbers stored as lists, most significant digit first?
Reverse both, add with a carry, reverse the result. Or use two stacks.
Reverse in k-groups: how do you leave a short last group alone?
Walk k steps from before first. Stop if you hit null.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Reverse Linked ListReverse a singly linked list by rewiring its next pointers, and return the new head.Easy
- Merge Two Sorted ListsSplice two sorted linked lists into one sorted list, reusing their nodes.Easy
- Remove Nth Node From End of ListDelete the n-th node counted from the end of a linked list in a single pass.Medium
- Reverse Linked List IIReverse only the nodes from position left to position right of a linked list, in one pass.Medium
- Reorder ListRearrange a list in place so it alternates first, last, second, second-to-last, and so on.Medium
- Reverse Nodes in k-GroupReverse a linked list k nodes at a time, leaving a final group shorter than k untouched.Hard
- Palindrome Linked ListDecide whether the values of a singly linked list read the same forwards and backwards, using O(1) extra space.Easy
In real systems
This pattern is not only for coding rounds. Here is where it does the work in a design interview.