Reverse a Linked List — Iterative and Recursive
The three-pointer pattern is the foundation of every linked list reversal. Understand it once and LC 206, 92, and 25 become variations on the same idea.

Reverse a Linked List — Iterative and Recursive
Every linked list interview will eventually ask you to reverse something. It might be the whole list, a segment between two positions, or groups of k nodes. The underlying operation is always the same — but the bugs candidates make are also always the same. Let's make sure you're not one of them.
The Pattern: Pointer Manipulation
Reversing a linked list is pointer bookkeeping. You have a chain of nodes pointing forward. Your job is to redirect each node's next pointer to point backward — without losing the rest of the list in the process.
The trap: curr.next = prev overwrites the only reference to the rest of the list. Do that before saving curr.next, and you've just orphaned every node after curr. This is the single most common bug in linked list reversal.
const next = curr.next BEFORE reassigning curr.next. If you forget this, you silently lose the rest of the list — no error, just a truncated output that's hard to debug.Visualizing the Iterative Approach
Problem 1: Reverse Entire Linked List (LC 206)
The foundation. Every other reversal problem builds on this.
Brute Force
You could collect node values into an array, reverse the array, then overwrite node values — but that requires O(n) space and doesn't actually reverse the list structure. In interviews, this approach is a red flag because it misses the point of the problem.
// Input: head = [1,2,3,4,5]
// Output: [5,4,3,2,1]
const reverseListBrute = (head) => {
const vals = [];
let curr = head;
// Collect all values
while (curr) {
vals.push(curr.val);
curr = curr.next;
}
// Overwrite in reverse order
curr = head;
let i = vals.length - 1;
while (curr) {
curr.val = vals[i--];
curr = curr.next;
}
return head;
};
// Time: O(n) | Space: O(n) — not great, and doesn't reverse pointersIterative (Optimal)
Three pointers: prev starts as null (the new tail needs to point to null), curr starts at head. On each iteration, save curr.next, redirect curr.next to prev, then advance both.
// Input: head = [1,2,3,4,5]
// Output: [5,4,3,2,1]
const reverseList = (head) => {
let prev = null;
let curr = head;
while (curr) {
const next = curr.next; // save before we lose it
curr.next = prev; // flip the pointer
prev = curr; // advance prev to current node
curr = next; // advance curr using saved reference
}
// When curr is null, prev is the new head
return prev;
};
// Time: O(n) | Space: O(1)After the loop, curr is null and prev is the last node you processed — which is the new head. Returning curr by mistake is one of the most common bugs here.
A common shorthand using destructuring (you'll see this in NeetCode solutions):
while (curr) {
[curr.next, prev, curr] = [prev, curr, curr.next];
}This is valid JavaScript — the right-hand side evaluates fully before any assignment happens — but write the explicit version in an interview unless you're 100% comfortable explaining the destructuring.
Recursive
The recursive approach reverses the rest of the list first, then fixes up the current node's pointer on the way back up the call stack.
// Input: head = [1,2,3,4,5]
// Output: [5,4,3,2,1]
const reverseListRecursive = (head) => {
// Base case: empty list or single node — already reversed
if (!head || !head.next) return head;
// Recurse: get the new head from the reversed sublist
const newHead = reverseListRecursive(head.next);
// At this point, head.next is the tail of the reversed sublist
// Make it point back to head
head.next.next = head;
// Sever head's forward pointer — critical to avoid a cycle
head.next = null;
return newHead; // propagate the new head unchanged
};
// Time: O(n) | Space: O(n) — implicit call stack depth equals list lengthThe head.next = null line trips people up. By the time we get here, head.next still points to the next node (the tail of the now-reversed sublist). If you don't sever that pointer, you create a cycle: node 2 points to node 1, and node 1 still points to node 2.
Problem 2: Reverse Linked List II (LC 92)
Now you need to reverse a segment: only the nodes between positions left and right (1-indexed).
// Input: head = [1,2,3,4,5], left = 2, right = 4
// Output: [1,4,3,2,5]
// ^^^^^ reversed: 2→3→4 becomes 4→3→2, node 5 untouchedThe trick is identifying the correct boundary nodes and reconnecting after the reversal.
const reverseBetween = (head, left, right) => {
// Dummy node eliminates the edge case where left = 1
// (we always have a node before the segment to anchor to)
const dummy = { val: 0, next: head };
// Walk to the node immediately BEFORE the segment
let anchor = dummy;
for (let i = 0; i < left - 1; i++) {
anchor = anchor.next;
}
// segmentTail will become the tail of the reversed segment
const segmentTail = anchor.next;
// Standard reversal, but only for (right - left + 1) iterations
let prev = anchor;
let curr = anchor.next;
for (let i = 0; i < right - left + 1; i++) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
// Reconnect: anchor points to the new segment head (prev)
// segment tail points to the node after the segment (curr)
anchor.next = prev;
segmentTail.next = curr;
return dummy.next;
};
// Time: O(n) | Space: O(1)Two boundary pointers you need before reversing:
anchor: the node right before the segment — it needs to point to the new segment head after reversalsegmentTail: the original segment head, which becomes the tail after reversal — it needs to point to whatever comes after positionright
Problem 3: Reverse Nodes in K-Group (LC 25)
The hard variant: reverse every group of k nodes. If the remaining nodes at the end are fewer than k, leave them as-is.
// Input: head = [1,2,3,4,5], k = 2
// Output: [2,1,4,3,5]
// ^ only 1 node left, fewer than k=2, leave it
const reverseKGroup = (head, k) => {
// Count k nodes forward — do we have a full group?
let count = 0;
let node = head;
while (node && count < k) {
node = node.next;
count++;
}
// Fewer than k nodes remaining — leave as-is
if (count < k) return head;
// Recurse on the remainder FIRST, get the new head for what follows
const reversedRemainder = reverseKGroup(node, k);
// Now reverse the current group of k nodes
// Attach the last node of this group to the already-reversed remainder
let prev = reversedRemainder;
let curr = head;
for (let i = 0; i < k; i++) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
// prev is the new head of the current group (was the k-th node)
return prev;
};
// Time: O(n) | Space: O(n/k) — recursion depth equals number of groupsThe recursion-first approach is elegant: solve the tail first, then reverse the current group and stitch it to the already-solved tail. The alternative is iterative with careful pointer tracking — more code, same complexity.
Complexity Cheat Sheet
| Problem | Approach | Time | Space |
|---|---|---|---|
| LC 206 Reverse List | Array collect + overwrite | O(n) | O(n) |
| LC 206 Reverse List | Iterative (3 pointers) | O(n) | O(1) |
| LC 206 Reverse List | Recursive | O(n) | O(n) |
| LC 92 Reverse Between | Iterative with dummy + anchors | O(n) | O(1) |
| LC 25 Reverse K-Group | Recursive + reversal | O(n) | O(n/k) |
Interview Tips
Say the three-pointer names out loud before you start. "I'll use prev, curr, and next." Naming your variables explicitly forces you to think through the algorithm before touching code, and gives the interviewer confidence that you have a plan.
Trace through a 3-node list. Before the interview, practice with 1 → 2 → 3. After one iteration: prev=1, curr=2. After two: prev=2, curr=3. After three: prev=3, curr=null — return prev. This 15-second mental trace catches most bugs.
For partial reversals, draw the before and after on the whiteboard. Identify anchor, segmentTail, and the first node after the segment before writing any code. The code almost writes itself once the diagram is right.
Comments (0)
No comments yet. Be the first to share your thoughts!