Reverse Linked List: Complete Deep Dive (Iterative & Recursive)

By DSA Prep Team · March 10, 2026 · 12 min read · Algorithms

Reverse Linked List is rated Easy β€” but it is the single most important linked list problem you will ever solve. Every harder linked list problem (Reverse in k-Group, Palindrome Linked List, Reorder List) uses this as a building block. If you don't understand the mechanics deeply, those harder problems become nearly impossible.

Our analysis of 10,385 verified interview questions across 259 companies shows it appeared 27 times across 13 companies β€” including Google, Meta, Apple, Microsoft, Bloomberg, ByteDance, and J.P. Morgan.

This guide goes beyond just showing you the code. We'll break down exactly why each pointer move happens in the iterative solution, how the call stack unwinds in the recursive solution, what the most common mistakes are and why they happen, and how this problem directly unlocks 6 harder variants.

Table of Contents

The Data: 13 Companies Ask This

MetricValue
Total Interview Appearances27 verified mentions
Companies That Ask It13 companies
DifficultyEasy
Core PatternsLinked List, Recursion

πŸ“Œ Companies That Ask This Problem

Google, Meta, Apple, Microsoft, Bloomberg, ByteDance, J.P. Morgan, Cisco, Expedia, Accenture, Deloitte, Luxoft, and NetApp.

Problem Statement

LeetCode #206 β€” Reverse Linked List

Given the head of a singly linked list, reverse the list and return the reversed list's head.

Examples
Input:  head = [1 β†’ 2 β†’ 3 β†’ 4 β†’ 5]   β†’   Output: [5 β†’ 4 β†’ 3 β†’ 2 β†’ 1]
Input:  head = [1 β†’ 2]                β†’   Output: [2 β†’ 1]
Input:  head = []                     β†’   Output: []

A singly linked list node only has a val and a next pointer. There is no prev pointer. The list only knows how to go forward. Your job is to make every node point backward instead β€” in-place, without creating a new list.

⚠️ The Core Constraint

You cannot simply "walk backward" through a singly linked list. Once you redirect curr.next, you permanently lose access to the rest of the list unless you saved it first. This is the entire challenge of the problem.

Why the Naive Approach Fails

Most beginners try something like this on their first attempt:

❌ Naive Attempt (Broken)
# What beginners try:
curr = head
while curr:
    curr.next = prev   # ← redirects the pointer
    prev = curr
    curr = curr.next   # ← curr.next is now prev (already changed!)
                       #   we've lost the rest of the list forever

The problem is the order of operations. The moment you write curr.next = prev, the original curr.next (the next node in the list) is gone. You can never reach it again. So on the next line, curr = curr.next moves curr to prev, not forward β€” creating an infinite loop or immediately terminating.

The fix is simple but crucial: save curr.next before you redirect anything. This is the entire secret of the iterative solution.

Iterative Solution: The Three-Pointer Mechanics

The iterative solution uses three pointers that each have a specific role:

At each iteration, you execute exactly four operations in this exact order:

  1. Save: next_node = curr.next β€” preserve the forward reference before breaking it
  2. Redirect: curr.next = prev β€” point current node backward
  3. Advance prev: prev = curr β€” the reversed portion now includes curr
  4. Advance curr: curr = next_node β€” move to the next unprocessed node

When curr becomes None, the entire list has been reversed and prev is pointing at the new head (the original last node). Return prev.

Full Step-by-Step Trace

Let's trace through [1 β†’ 2 β†’ 3 β†’ 4 β†’ 5] completely:

Full Iterative Trace β€” [1 β†’ 2 β†’ 3 β†’ 4 β†’ 5]
Initial state:
  prev = None
  curr = 1

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 1:  curr = Node(1)
  next_node = 2          ← save: next_node points to Node(2)
  curr.next = None       ← redirect: 1 now points to None (prev)
  prev = Node(1)         ← advance prev: reversed portion is [1 β†’ None]
  curr = Node(2)         ← advance curr: move forward

  State: None ← 1    2 β†’ 3 β†’ 4 β†’ 5
                ↑prev  ↑curr

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 2:  curr = Node(2)
  next_node = 3          ← save Node(3)
  curr.next = Node(1)    ← redirect: 2 now points to 1
  prev = Node(2)         ← advance prev: reversed portion is [2 β†’ 1 β†’ None]
  curr = Node(3)         ← advance curr

  State: None ← 1 ← 2    3 β†’ 4 β†’ 5
                    ↑prev  ↑curr

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 3:  curr = Node(3)
  next_node = 4
  curr.next = Node(2)    ← 3 now points to 2
  prev = Node(3)
  curr = Node(4)

  State: None ← 1 ← 2 ← 3    4 β†’ 5
                        ↑prev  ↑curr

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 4:  curr = Node(4)
  next_node = 5
  curr.next = Node(3)    ← 4 now points to 3
  prev = Node(4)
  curr = Node(5)

  State: None ← 1 ← 2 ← 3 ← 4    5
                            ↑prev  ↑curr

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 5:  curr = Node(5)
  next_node = None       ← save None (end of list)
  curr.next = Node(4)    ← 5 now points to 4
  prev = Node(5)
  curr = None            ← advance curr: now None

  State: None ← 1 ← 2 ← 3 ← 4 ← 5
                                ↑prev  curr=None

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
curr is None β†’ loop ends β†’ return prev = Node(5)

Result: 5 β†’ 4 β†’ 3 β†’ 2 β†’ 1 β†’ None  βœ…

Iterative Code

Python 3
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def reverseList(head: ListNode) -> ListNode:
    prev = None
    curr = head

    while curr:
        next_node = curr.next  # Step 1: save β€” MUST come first
        curr.next = prev       # Step 2: redirect β€” break forward, point back
        prev = curr            # Step 3: advance prev β€” grow the reversed portion
        curr = next_node       # Step 4: advance curr β€” move to next unprocessed node

    # When curr is None, prev points to the new head
    return prev
Java
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;

        while (curr != null) {
            ListNode nextNode = curr.next; // Step 1: save
            curr.next = prev;              // Step 2: redirect
            prev = curr;                   // Step 3: advance prev
            curr = nextNode;               // Step 4: advance curr
        }

        return prev; // new head
    }
}
JavaScript
var reverseList = function(head) {
    let prev = null;
    let curr = head;

    while (curr) {
        const nextNode = curr.next; // Step 1: save
        curr.next = prev;           // Step 2: redirect
        prev = curr;                // Step 3: advance prev
        curr = nextNode;            // Step 4: advance curr
    }

    return prev; // new head
};

Recursive Solution: Understanding the Call Stack

The recursive approach is more elegant but harder to understand because the key action happens on the way back up the call stack, not on the way down.

Here is the mental model in two phases:

Phase 1 β€” Descending (going down the call stack):

We keep calling reverseList(head.next) recursively, doing nothing except moving forward, until we hit the base case: a node with no next (the last node). That last node becomes our new_head β€” the head of the final reversed list. We return it all the way up unchanged.

Phase 2 β€” Ascending (unwinding the call stack):

Now, on the way back up, at each stack frame we have access to head (the current node) and we know head.next still points to the next node (which is already part of the reversed suffix). We do two things:

Then we return new_head unchanged all the way up. Every stack frame does the same two operations β€” wire the current node into the reversed chain, break the forward link β€” and passes the same new_head up.

Recursive Unwinding Trace

Recursive Trace β€” [1 β†’ 2 β†’ 3]
━━━━━━━━━━━━━━━━ DESCENDING (building call stack) ━━━━━━━━━━━━━━━━

reverseList(1)          β†’ calls reverseList(2)
  reverseList(2)        β†’ calls reverseList(3)
    reverseList(3)      β†’ head.next is None β†’ BASE CASE
                          return Node(3) as new_head

━━━━━━━━━━━━━━━━ ASCENDING (unwinding call stack) ━━━━━━━━━━━━━━━━

Back in reverseList(2):   head = Node(2), new_head = Node(3)
  At this point:  1 β†’ 2 β†’ 3 β†’ None   (original forward links still exist)

  head.next.next = head
  β†’ Node(3).next = Node(2)    ← 3 now points back to 2
  β†’ State: 1 β†’ 2 ← 3    (2 still points forward to 3 too β€” cycle risk!)

  head.next = None
  β†’ Node(2).next = None       ← break 2's forward link
  β†’ State: 1 β†’ 2   3 β†’ 2 β†’ None   ← wait, let's re-read...
  β†’ Actually: 1 β†’ 2 β†’ None  and  3 β†’ 2 β†’ None
              (2's forward link is broken, 3 now owns 2)

  return new_head (Node 3) ← pass it up unchanged

━━━━━━━━━━━━━━━━

Back in reverseList(1):   head = Node(1), new_head = Node(3)
  At this point: 1 β†’ 2 β†’ None  (2 was already severed from 3)
                 3 β†’ 2 β†’ None  (reversed suffix is built)

  head.next.next = head
  β†’ Node(2).next = Node(1)    ← 2 now points back to 1
  β†’ State: 3 β†’ 2 β†’ 1  (1 still forward-links... to null already)

  head.next = None
  β†’ Node(1).next = None       ← confirm 1 points to null (already was)

  return new_head (Node 3)

━━━━━━━━━━━━━━━━

Final result: 3 β†’ 2 β†’ 1 β†’ None  βœ…
new_head = Node(3) returned to original caller

πŸ’‘ Why head.next = None is Critical

After head.next.next = head, both head and head.next point to each other β€” a cycle. The line head.next = None breaks head's forward link, converting the cycle into a clean chain. Forgetting this line creates an infinite loop when traversing the result.

Recursive Code

Python 3
def reverseList(head: ListNode) -> ListNode:
    # Base case: empty list or single node β€” already reversed
    if not head or not head.next:
        return head

    # Phase 1 (descend): recurse all the way to the last node
    # new_head will be the last node, returned unchanged all the way up
    new_head = reverseList(head.next)

    # Phase 2 (ascend): wire current node into the reversed chain
    # head.next is the node immediately after head β€” it's already in the reversed suffix
    head.next.next = head   # make that node point BACK to head
    head.next = None        # break head's original forward link (prevents cycle)

    # Pass new_head (the last node) unchanged up the call stack
    return new_head
Java
class Solution {
    public ListNode reverseList(ListNode head) {
        // Base case
        if (head == null || head.next == null) return head;

        // Descend to the end, capture new head (last node)
        ListNode newHead = reverseList(head.next);

        // Ascend: wire current node into reversed chain
        head.next.next = head; // node after head points back to head
        head.next = null;      // break forward link, prevent cycle

        return newHead; // propagate unchanged
    }
}
JavaScript
var reverseList = function(head) {
    // Base case
    if (!head || !head.next) return head;

    // Descend to end, capture new head
    const newHead = reverseList(head.next);

    // Ascend: wire current node into reversed chain
    head.next.next = head; // node after head points back to head
    head.next = null;      // break forward link, prevent cycle

    return newHead; // propagate unchanged
};

Common Mistakes and Why They Happen

⚠️ Mistake 1: Wrong Order of Operations (Iterative)

Writing curr.next = prev before saving next_node = curr.next. Once you redirect curr.next, the original next node is permanently lost. The save step must always come first.

❌ Wrong: curr.next = prev; next_node = curr.next; ...
βœ… Right: next_node = curr.next; curr.next = prev; ...

⚠️ Mistake 2: Forgetting head.next = None (Recursive)

After head.next.next = head, nodes point to each other creating a cycle: head β†’ next β†’ head β†’ next β†’ .... Without head.next = None, printing or traversing the result causes an infinite loop. The original tail (node 1 in a 5-node list) already has next = None from its base case return, but every other node needs it explicitly set.

⚠️ Mistake 3: Returning head Instead of prev (Iterative)

After the while loop, head still points to the original first node (now the tail). prev is the original last node (now the head). Always return prev.

⚠️ Mistake 4: Missing the Empty List Edge Case

If head is None (empty list), both solutions handle it correctly β€” the iterative loop doesn't execute and returns prev = None; the recursive base case returns None. But if you write a custom base case as if not head.next only, you'll get an AttributeError on None.next. Always check if not head or not head.next.

Complexity Analysis

ApproachTimeSpaceNotes
IterativeO(n)O(1)Visits each node once. Only 3 pointer variables regardless of list size.
RecursiveO(n)O(n)Each call adds a frame to the call stack. For n=5000 nodes, that's 5000 stack frames β€” risk of stack overflow on very large inputs.

In interviews, always lead with the iterative solution. The recursive solution is elegant and worth knowing, but the O(n) space cost from the call stack is a real drawback that interviewers will ask about.

Linked List Reversal Variants

Once you own LC #206, these six problems become significantly more approachable β€” they all use reversal as a core subroutine:

ProblemHow It Builds on #206Difficulty
LC #92 β€” Reverse Linked List IIReverse only nodes from position left to right. Requires finding the sublist boundaries first, then applying the same three-pointer reversal on just that segment.Medium
LC #25 β€” Reverse Nodes in k-GroupReverse every k consecutive nodes. Checks if k nodes remain, reverses them, reconnects groups. Uses reversal as a helper function called repeatedly.Hard
LC #234 β€” Palindrome Linked ListFind the middle (Floyd's algorithm), reverse the second half, compare both halves node by node, then optionally restore. Reversal is one of three steps.Easy
LC #143 β€” Reorder ListFind middle, reverse second half, interleave first and second halves. Identical reversal subproblem as #234.Medium
LC #24 β€” Swap Nodes in PairsSpecial case of k-group reversal with k=2. Can be solved iteratively by manipulating 4 pointers per pair.Medium
LC #61 β€” Rotate ListFind the new tail (length - k % length steps in), reverse the connection. Uses reversal concept but solves it via pointer reconnection instead.Medium

πŸ’‘ The Learning Order That Makes Everything Click

Do them in this sequence: #206 β†’ #92 β†’ #234 β†’ #143 β†’ #24 β†’ #25. Each problem adds exactly one new concept on top of the previous. By the time you reach #25 (Hard), you'll find it manageable because you've built the foundation incrementally.

How to Actually Remember This

The Iterative Mnemonic: SRAA

Remember the four operations in order: Save β†’ Redirect β†’ Advance prev β†’ Advance curr. If you forget the order in an interview, say "SRAA" mentally and you'll reconstruct the solution correctly every time.

The Recursive Mental Model: "Reach the end, wire backward on the way up"

Phase 1 is passive β€” just descend to the last node. Phase 2 is active β€” head.next.next = head wires the node in, head.next = None breaks the cycle. Two lines. Same two lines at every stack frame.

βœ… Spaced Repetition Schedule

  1. Today: Implement both iterative and recursive from scratch. Trace through [1β†’2β†’3] on paper for the recursive version β€” draw the call stack explicitly.
  2. Day 3: Solve LC #92 (Reverse Linked List II). The key addition: find the node just before position left, apply the three-pointer reversal for right - left steps, then reconnect the four boundary pointers.
  3. Day 7: Solve LC #234 (Palindrome Linked List). Three distinct steps: find middle with slow/fast pointers, reverse second half, compare.
  4. Day 14: Solve LC #143 (Reorder List) and LC #24 (Swap Nodes in Pairs).
  5. Day 21: Solve LC #25 (Reverse Nodes in k-Group) β€” the final boss of linked list reversal, commonly asked at Google and Meta.

Tag on DSAPrep.dev under Linked List and Recursion.

Track on DSAPrep.dev β†’