LearnDSA PatternsIn-Place Linked List ReversalCase Study

LeetCode 206 · Easy · Three-Pointer Reversal · TypeScript

Reverse Linked List:
4 Bugs to a Clean Solution

Not a tutorial — a real debugging log. Five attempts, four distinct bugs, each one reproduced with a concrete failing input before it was fixed.

Scroll to follow the journey

The Problem

LeetCode 206 — Reverse Linked List

Given the head of a singly linked list, reverse the list in place — every node's next pointer must flip to point at the node that came before it — and return the new head.

Input

1→2→3→4→5→null

Output

5→4→3→2→1→null

Input

[1,2] (two nodes)

Output

[2,1]

Input

[] (empty list)

Output

[]

The Journey

Attempt by Attempt

Every bug below was reproduced with a real failing input before being fixed — not just described.

1 prev Reset Inside the Loop — Every Pass Starts From Scratch 🐛 Buggy
function reverseList(head: ListNode | null): ListNode | null {
  let prev = null;
  while (head) {
    prev = null;            // reset every pass
    const temp = head.next;
    head.next = prev;
    prev = head;
    head = temp;
  }
  return prev;
}

Hand-tracing 1→2→3→null on the very first attempt shows the reversed list never actually forms — every node ends up pointing to null instead of the previous node.

prev is the one piece of state that must survive between iterations — it's the growing reversed list. Resetting it to null at the top of every pass throws that accumulated state away before it's used, so head.next = prev always writes null instead of the real previous node.

Proof — trace on 1→2→3→null:

iter1: prev reset→null. temp=N1(2). N0.next=null (prev). prev=N0(1). head=N1(2).
iter2: prev reset→null AGAIN. temp=N2(3). N1.next=null (prev) — not N0! prev=N1(2). head=N2(3).
iter3: prev reset→null AGAIN. temp=null. N2.next=null (prev). prev=N2(3). head=null → exit.
return prev = N2(3).
Expected: 3→2→1→null. Got: N0.next=null, N1.next=null, N2.next=null — three disconnected single nodes, only node 3 returned.
Fix →

Declare prev once, before the loop, and never reset it inside — it must carry the reversal forward across every iteration.

2 Fixed the Reset — Now temp.next Skips a Node and Crashes 🐛 Buggy
function reverseList(head: ListNode | null): ListNode | null {
  let prev = null;
  while (head) {
    const temp = head.next;
    head.next = prev;
    prev = head;
    head = temp.next;       // temp already IS the next node
  }
  return prev;
}

prev now persists correctly across iterations — but stress-testing shows a node silently disappears from the traversal, and the function eventually throws.

temp was saved as head.next specifically so head = temp advances by exactly one node. Reading .next off temp again jumps an extra node ahead each pass — one node's link is never touched at all — and once temp itself becomes null near the end of the list, temp.next throws instead of the loop ending cleanly.

Proof — trace on 1→2→3→null:

iter1: temp=N1(2). N0.next=null (prev). prev=N0(1).
       head = temp.next = N1.next = N2(3)  ← node 2 is skipped entirely, its .next is never reversed!
iter2: temp=head.next=N2.next=null. N2.next=prev=N0(1). prev=N2(3).
       head = temp.next = null.next → CRASH: Cannot read properties of null (reading 'next')
Expected: 3→2→1→null. Got: TypeError before the function can return, and node 2 was silently dropped from the traversal.
Fix →

temp already IS the next node — advance with head = temp, not head = temp.next.

3 Guarded the Wrong Thing — Now It Hangs on the Last Node 🐛 Buggy
function reverseList(head: ListNode | null): ListNode | null {
  let prev = null;
  while (head) {
    const temp = head.next;
    if (temp) {
      head.next = prev;
      prev = head;
      head = temp;
    }
  }
  return prev;
}

Pointers advance correctly now — but running on any real list never returns; it hangs.

The if (temp) guard was likely added defensively, but it gates the very steps that make the loop progress, including head = temp. Once temp is null (the last node), the entire body — the reversal, the advance of prev, and the advance of head — is skipped. head stays frozen on the last node forever, and while (head) never becomes false.

Proof — trace on 1→2→3→null:

iter1: head=N0(1). temp=N1(2). temp truthy → N0.next=null, prev=N0, head=N1(2).
iter2: head=N1(2). temp=N2(3). temp truthy → N1.next=N0, prev=N1, head=N2(3).
iter3: head=N2(3). temp=null. temp falsy → guard body SKIPPED entirely. head still = N2(3).
iter4: head=N2(3) (unchanged). temp=null again. guard skipped again. head still = N2(3).
...repeats forever — head never becomes null, while (head) never exits.
Expected: return 3→2→1→null. Got: infinite loop, the function never returns.
Fix →

Remove the guard entirely — the loop's own while (head) condition already handles termination correctly. Nothing inside the body should be conditionally skipped.

4 Loop Is Correct — Now It Returns the Wrong Pointer 🐛 Buggy
function reverseList(head: ListNode | null): ListNode | null {
  let prev = null;
  while (head) {
    const temp = head.next;
    head.next = prev;
    prev = head;
    head = temp;
  }
  return head;              // head is null here!
}

The reversal logic itself is finally correct — every link flips exactly once — but the function always returns null, no matter the input.

The while (head) loop can only exit once head has become null — that's the exit condition, by construction. Returning head at that point always returns null, discarding the reversed list that prev spent the whole loop quietly building. head was never the answer; it was only ever the variable steering the loop forward.

Proof — trace on 1→2→3→null:

iter1: temp=N1(2). N0.next=null (prev). prev=N0(1). head=N1(2).
iter2: temp=N2(3). N1.next=N0 (prev). prev=N1(2). head=N2(3).
iter3: temp=null. N2.next=N1 (prev). prev=N2(3). head=null → loop exits (this is WHY it exits).
return head → null.
Expected: 3→2→1→null (the correctly-built reversed list, sitting in prev). Got: null, every time, regardless of input.
Fix →

Return prev, not head. Ask: which variable is my answer, and which was just steering the loop?

5 Verified, and Simplified ✅ ✅ Correct
// O(N) time, O(1) space — iterative three-pointer reversal
function reverseList(head: ListNode | null): ListNode | null {
  let prev = null;

  while (head) {
    const temp = head.next;   // 1. save the rest of the list
    head.next = prev;         // 2. reverse the link
    prev = head;              // 3. advance prev
    head = temp;              // 4. advance head
  }

  return prev;          // head is null — prev holds the answer
}

// 1→2→3→null (no cycle)
console.log(reverseList(list)?.value); // → 3

All four issues are resolved: prev persists across iterations, head advances by exactly one node via temp (not temp.next), no conditional guard skips the loop's own progression, and the function returns prev — the pointer that actually carries the reversed list.

Proof — full trace on 1→2→3→null:

Initial: prev=null, head=N0(1).
iter1: temp=N1(2). N0.next=null (prev). prev=N0(1). head=N1(2).
iter2: temp=N2(3). N1.next=N0 (prev). prev=N1(2). head=N2(3).
iter3: temp=null. N2.next=N1 (prev). prev=N2(3). head=null → loop exits.
return prev = N2(3) → 3→2→1→null ✅
Also verified: empty list ([]) returns null immediately; single node ([1]) returns that same node unchanged.

Interactive

Reversal Visualizer

Watch prev and curr flip every link on 1→2→3→null step-by-step.

1 2 3 prev curr

Initial: prev=null, curr=1. List: 1→2→3→null.

prev curr original link reversed link

Lessons Learned

Key Takeaways

🔍

Trace a small example by hand before trusting the code

Hand-tracing 1→2→3→null across all four attempts is what surfaced every one of these bugs — the reset, the skipped node, the hang, and the wrong return value.

🧷

State that must persist across iterations lives outside the loop

prev is the accumulating answer. Reset it — or re-declare it — inside the loop body, and every iteration starts from scratch instead of building on the last one.

➡️

temp already IS the next node — don't call .next on it again

temp was saved as head.next for exactly one reason: to let head = temp advance by one node. Reading .next off it a second time skips a node and eventually crashes on null.

🛡️

Never let a guard skip the loop's own progression

An if (temp) check that wraps the entire body — including the line that advances head — stalls the loop on the last node forever. Guards should protect against invalid state, not gate the loop's ability to move forward.

🎯

Ask which variable is the answer, and which was just steering

head reaching null is the reason the loop exits — it was never meant to hold the result. prev quietly accumulated the real answer the entire time.

Final Result

The Verified Solution

Three pointers, one pass — save what's ahead, flip the link, advance both.

Final — Attempt 5
// O(N) time, O(1) space — iterative three-pointer reversal
function reverseList(head: ListNode | null): ListNode | null {
  let prev = null;

  while (head) {
    const temp = head.next;   // 1. save the rest of the list
    head.next = prev;         // 2. reverse the link
    prev = head;              // 3. advance prev
    head = temp;              // 4. advance head
  }

  return prev;          // head is null — prev holds the answer
}

// 1→2→3→null (no cycle)
console.log(reverseList(list)?.value); // → 3
Solution Time Space Verified
Iterative Three-Pointer Reversal (Attempt 5) Verified ✓ O(N) O(1) Multi-node, two-node, empty, and single-node cases

Reuse This

Template: In-Place Reversal (prev / curr / temp)

The generic shape behind this solution — adapt it to any "reverse this chain" question.

Pseudocode
function reverseList(head):
    prev = null

    while head is not null:
        temp = head.next          // 1. save the rest of the list first
        head.next = prev          // 2. reverse the current link
        prev = head               // 3. advance prev forward
        head = temp                // 4. advance head forward

    return prev                    // head is null here — prev holds the answer

When to reach for this

Any "flip the direction of this chain" question on a linked structure — O(1) space beats copying into a stack or array.

The trap this journey hit

Losing track of which variable is the answer (prev) versus which one merely steers the loop (head) — the same mix-up caused three of the four bugs above.

Test Yourself

Quiz: Check Your Understanding

Question 1 of 6 Score: 0
Why must prev be declared before the while loop instead of inside it? basic

Keep Learning

Bugs are part of the process 🎉

Four real bugs, four real fixes — that's what getting to a correct in-place reversal solution actually looks like.

Related: try LeetCode 92 — Reverse Linked List II (reverse only a sublist) or LeetCode 234 — Palindrome Linked List (uses this exact reversal as a building block).