DevLift
Back to Blog

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.

Admin
August 2, 20264 min read11 views
Reverse a Linked List — Iterative and Recursive

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.

⚠️
Always save 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

Rendering diagram...

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 pointers

Iterative (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 length

The 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.

💡
In interviews, prefer the iterative approach. It's O(1) space, no stack overflow risk on long lists, and easier to trace through. Know the recursive version because interviewers often ask "can you do this recursively?" as a follow-up.

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 untouched

The 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 reversal
  • segmentTail: the original segment head, which becomes the tail after reversal — it needs to point to whatever comes after position right
Always use a dummy node for problems where the head itself might change or where you need to prepend/reconnect before the first real node. It eliminates a whole class of edge cases without adding complexity.

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 groups

The 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

ProblemApproachTimeSpace
LC 206 Reverse ListArray collect + overwriteO(n)O(n)
LC 206 Reverse ListIterative (3 pointers)O(n)O(1)
LC 206 Reverse ListRecursiveO(n)O(n)
LC 92 Reverse BetweenIterative with dummy + anchorsO(n)O(1)
LC 25 Reverse K-GroupRecursive + reversalO(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!

Related Articles

Two pointers, one pass, O(n) — learn how the variable-size sliding window grows and shrinks to solve substring and subarray problems efficiently.
AdminAugust 2, 20264 min read
Sliding window maximum is the classic follow-up to every sliding window problem. Learn the monotone deque pattern in JavaScript with LeetCode 239, 1438, and 1696.
AdminAugust 2, 20268 min read
Two matrix search problems, two completely different optimal approaches. Learn when to use virtual 1D binary search vs the staircase elimination — the distinction that trips up most candidates.
AdminAugust 2, 20266 min read