Home DSA Reverse a Linked List: Iterative, Recursive & In-Place Explained

Reverse a Linked List: Iterative, Recursive & In-Place Explained

In Plain English 🔥
Imagine you have a train where each car only knows which car is directly behind it. Reversing the train means making every car point to the one that used to be in front of it instead. You can't pick the whole train up and flip it — you have to walk car by car and re-hook the connections one at a time. That's exactly what reversing a linked list is: rewiring pointers, not moving data.
⚡ Quick Answer
Imagine you have a train where each car only knows which car is directly behind it. Reversing the train means making every car point to the one that used to be in front of it instead. You can't pick the whole train up and flip it — you have to walk car by car and re-hook the connections one at a time. That's exactly what reversing a linked list is: rewiring pointers, not moving data.

Reversing a linked list is one of those problems that shows up everywhere — technical interviews at top companies, real-world browser history navigation, undo-redo systems in text editors, and even certain networking packet-processing pipelines. It's not a toy problem. It's a litmus test for whether you truly understand how pointer-based data structures work under the hood, not just how to call methods on them.

The core challenge is that a singly linked list is a one-way street. Every node only knows its next neighbour — it has no idea who's behind it. To reverse the list, you have to actively build those backward connections as you walk forward, because once you move past a node without saving a reference to it, it's gone. That's the puzzle: how do you rewire something while you're still walking through it?

By the end of this article you'll have a rock-solid mental model of both the iterative (loop-based) and recursive approaches, know exactly when to use each one, understand the space and time trade-offs, and be able to walk through the logic on a whiteboard without hesitation. Whether you're prepping for an interview or just trying to actually understand this classic problem, you're in the right place.

What You're Actually Changing When You 'Reverse' a List

Before writing a single line of code, let's be precise about what reversal means. You are NOT shuffling node values around. You're changing the direction of the next pointers inside each node.

Suppose your list is: 1 → 2 → 3 → 4 → null. After reversal it becomes: 4 → 3 → 2 → 1 → null. The nodes themselves haven't moved in memory. The integer values are still sitting in the same objects. What changed is that node-2's next now points to node-1 instead of node-3, node-3's next points to node-2 instead of node-4, and so on.

This distinction matters enormously. If you ever find yourself thinking 'I'll just collect the values into an array, reverse the array, and write values back', you're solving a completely different problem — one that falls apart the moment nodes carry complex objects instead of simple integers. Real reversal is about pointer surgery, not data shuffling.

The head pointer also changes. After reversal, the old tail (node-4) becomes the new head. Your code must return or update this new head, or the caller will still be pointing at node-1 thinking it's the start of the list.

LinkedListNode.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041
// This file defines the building block we'll use throughout the article.
// A generic Node that holds an integer value and a reference to the next node.
public class LinkedListNode {

    int value;                  // The data this node holds
    LinkedListNode next;        // Pointer to the next node in the chain (null if tail)

    // Constructor — creates a node with no successor yet
    public LinkedListNode(int value) {
        this.value = value;
        this.next = null;       // Every node starts as a standalone island
    }

    // ─── Helper: build a linked list from an array of integers ───────────────
    public static LinkedListNode buildList(int[] values) {
        if (values == null || values.length == 0) return null;

        LinkedListNode head = new LinkedListNode(values[0]); // First node becomes head
        LinkedListNode current = head;

        for (int i = 1; i < values.length; i++) {
            current.next = new LinkedListNode(values[i]); // Chain each new node
            current = current.next;                       // Advance the pointer
        }
        return head;
    }

    // ─── Helper: print the list so we can verify results ────────────────────
    public static void printList(LinkedListNode head) {
        LinkedListNode current = head;
        StringBuilder output = new StringBuilder();

        while (current != null) {
            output.append(current.value);
            if (current.next != null) output.append(" → "); // Arrow between nodes
            current = current.next;
        }
        output.append(" → null");
        System.out.println(output);
    }
}
▶ Output
// No output — this is the foundational class used by all examples below.
🔥
Mental Model:Draw five boxes on paper with arrows between them. Now physically erase each arrow and redraw it pointing the other way. That physical act of redrawing arrows is *exactly* what your code will do — one node at a time.

The Iterative Approach: Three Pointers and a Walking Loop

The iterative solution is the one you should reach for first in production code and interviews. It runs in O(n) time and O(1) space — it uses no extra memory regardless of list size. The trick is maintaining three pointers simultaneously as you walk the list: one pointing behind you (previousNode), one on your current position (currentNode), and one looking ahead (nextNode).

Here's the exact sequence at each step: 1. Save the next node before you break its link (because you're about to overwrite it). 2. Reverse current's pointer to aim at previous. 3. Slide previous up to current. 4. Slide current up to the saved next.

Repeat until current falls off the end of the list (becomes null). At that point, previous is sitting on what used to be the tail — which is now the new head. Return previous.

The step that trips up almost everyone is step 1. If you forget to save next before reversing the pointer, you permanently lose the rest of the list. There's no way back. Think of it like a construction crew closing a bridge behind them — before you destroy the old bridge, make sure you've recorded where it led.

IterativeReversal.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
public class IterativeReversal {

    /**
     * Reverses a singly linked list in-place using three pointers.
     * Time:  O(n) — visits every node exactly once
     * Space: O(1) — only three pointer variables, no extra data structures
     *
     * @param  head  The current head of the list
     * @return       The new head (old tail) after reversal
     */
    public static LinkedListNode reverseIterative(LinkedListNode head) {

        LinkedListNode previousNode = null;    // Starts as null — new tail points to null
        LinkedListNode currentNode  = head;    // Start walking from the head

        while (currentNode != null) {          // Keep going until we fall off the end

            // STEP 1: Save next BEFORE we destroy the forward link
            LinkedListNode nextNode = currentNode.next;

            // STEP 2: Reverse the pointer — current now points BACKWARD
            currentNode.next = previousNode;

            // STEP 3: Slide the 'previous' window forward
            previousNode = currentNode;

            // STEP 4: Slide the 'current' window forward using our saved reference
            currentNode = nextNode;
        }

        // When the loop ends, currentNode is null and previousNode is the new head
        return previousNode;
    }

    // ─── Demo ──────────────────────────────────────────────────────────────────
    public static void main(String[] args) {

        // Build: 1 → 2 → 3 → 4 → 5 → null
        LinkedListNode head = LinkedListNode.buildList(new int[]{1, 2, 3, 4, 5});

        System.out.print("Before reversal: ");
        LinkedListNode.printList(head);        // Show the original list

        LinkedListNode newHead = reverseIterative(head);

        System.out.print("After  reversal: ");
        LinkedListNode.printList(newHead);     // Show the reversed list

        // Edge case: single element list
        LinkedListNode singleNode = new LinkedListNode(42);
        LinkedListNode reversedSingle = reverseIterative(singleNode);
        System.out.print("Single node:     ");
        LinkedListNode.printList(reversedSingle);

        // Edge case: null (empty list)
        LinkedListNode reversedNull = reverseIterative(null);
        System.out.println("Null list:       " + reversedNull); // Should print null
    }
}
▶ Output
Before reversal: 1 → 2 → 3 → 4 → 5 → null
After reversal: 5 → 4 → 3 → 2 → 1 → null
Single node: 42 → null
Null list: null
⚠️
Interview Gold:Interviewers love to ask 'what happens with an empty list or a single-element list?' Run through your loop mentally: if head is null, currentNode starts null, the while-loop never runs, previousNode stays null, and you return null. For a single node: one iteration runs, previous becomes that node, current becomes null, loop ends, you return that node unchanged. Always verify your edge cases out loud — it signals senior-level thinking.

The Recursive Approach: Trusting the Call Stack to Do the Work

The recursive solution is elegant and often asked about in interviews specifically to test whether you can reason about the call stack. The idea: if you trust that reverseRecursive(head.next) correctly reverses everything from node-2 onward, all you need to do is fix the one connection between node-1 and node-2 after that call returns.

After the recursive call returns, head.next is still pointing at node-2, and node-2 (which is now the tail of the reversed sublist) needs to point back at head. So you do head.next.next = head to make node-2 point at node-1, then head.next = null to make node-1 the new tail.

The base case is crucial: if the list is empty or has just one node, there's nothing to reverse — return head directly. Without this, you'll get a NullPointerException when you try to access head.next.next on a null.

The trade-off is space. Each recursive call adds a frame to the call stack. For a list of 10,000 nodes, you get 10,000 stack frames. This is O(n) space — and on very large lists it can cause a StackOverflowError. In interviews, always mention this trade-off even if they don't ask.

RecursiveReversal.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
public class RecursiveReversal {

    /**
     * Reverses a singly linked list using recursion.
     * Time:  O(n) — every node is visited once across all stack frames
     * Space: O(n) — one stack frame per node (WATCH OUT on large lists)
     *
     * @param  head  The current head of the list (or sublist in recursive calls)
     * @return       The new head of the fully reversed list
     */
    public static LinkedListNode reverseRecursive(LinkedListNode head) {

        // BASE CASE: empty list or single node — nothing to reverse, return as-is
        if (head == null || head.next == null) {
            return head; // This node becomes the new head of the reversed list
        }

        // RECURSIVE STEP: trust that everything from head.next onward gets reversed
        // newHead will be the last node of the original list (our eventual new head)
        LinkedListNode newHead = reverseRecursive(head.next);

        // At this point, head.next still points to the node just after head.
        // That node is now the TAIL of the reversed sublist.
        // We need it to point back at head to complete the reversal.
        head.next.next = head;  // Ex: if head=1 and head.next=2, make 2 → 1

        // head is now the tail of the fully reversed list, so it must point to null
        head.next = null;

        // Bubble the new head all the way back up the call stack
        return newHead;
    }

    // ─── Demo ──────────────────────────────────────────────────────────────────
    public static void main(String[] args) {

        // Build: 10 → 20 → 30 → 40 → null
        LinkedListNode head = LinkedListNode.buildList(new int[]{10, 20, 30, 40});

        System.out.print("Before reversal: ");
        LinkedListNode.printList(head);

        LinkedListNode newHead = reverseRecursive(head);

        System.out.print("After  reversal: ");
        LinkedListNode.printList(newHead);

        // Verify edge cases
        System.out.println("Null input:      " + reverseRecursive(null));

        LinkedListNode solo = new LinkedListNode(99);
        System.out.print("Single node:     ");
        LinkedListNode.printList(reverseRecursive(solo));
    }
}
▶ Output
Before reversal: 10 → 20 → 30 → 40 → null
After reversal: 40 → 30 → 20 → 10 → null
Null input: null
Single node: 99 → null
⚠️
Watch Out:The line `head.next.next = head` is where most people freeze on a whiteboard. Before writing it, ask: 'What is head.next right now?' It's still the node immediately after head in the ORIGINAL order. After the recursive call, that node became the tail of the reversed sublist. Making it point back at head (`head.next.next = head`) stitches the remaining connection. Then `head.next = null` prevents a cycle. Mess up that order and you get an infinite loop.

Reversing a Sublist: Real-World Extension for Interviews

Pure list reversal is foundational, but a more powerful — and more commonly asked — variant is: reverse only the nodes between positions left and right (1-indexed). This shows up in LeetCode 92 and regularly in FAANG-level interviews. It also mirrors real use cases: reversing a specific segment of a navigation history, or reordering a subsection of a task queue without touching the rest.

The approach: walk to the node just before position left (call it beforeLeft), then run your three-pointer reversal for exactly (right - left) steps inside the sublist, then reconnect the reversed segment back into the original list.

There are four connection points to manage: beforeLeft's next must point to the new front of the reversed segment, and the old front (now the tail of the reversed segment) must point to the node that was at position right+1. Getting these reconnections right requires careful tracking of two extra references: the node that enters the reversal (becomes the tail) and the node that exits it (becomes the new front).

SublistReversal.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
public class SublistReversal {

    /**
     * Reverses only the nodes between positions 'left' and 'right' (1-indexed).
     * Everything outside that range stays untouched.
     *
     * Example: list=12345, left=2, right=414325
     *
     * Time:  O(n)
     * Space: O(1)
     */
    public static LinkedListNode reverseSublist(
            LinkedListNode head, int left, int right) {

        if (head == null || left == right) return head; // Nothing to reverse

        // Dummy node before the real head — simplifies edge cases where left=1
        LinkedListNode dummy = new LinkedListNode(0);
        dummy.next = head;

        // Walk to the node just BEFORE the reversal window
        LinkedListNode beforeLeft = dummy;
        for (int step = 1; step < left; step++) {
            beforeLeft = beforeLeft.next; // Move (left-1) steps from dummy
        }

        // 'sublistHead' is the first node entering the reversal zone
        // After reversal it will become the TAIL of the reversed segment
        LinkedListNode sublistHead = beforeLeft.next;

        // Run the standard three-pointer reversal for (right - left) iterations
        LinkedListNode previousNode = null;
        LinkedListNode currentNode  = sublistHead;

        for (int step = 0; step <= right - left; step++) {
            LinkedListNode nextNode = currentNode.next; // Save next before overwriting
            currentNode.next = previousNode;            // Reverse the pointer
            previousNode = currentNode;                 // Slide previous forward
            currentNode = nextNode;                     // Slide current forward
        }

        // At this point:
        //   previousNode → new front of the reversed segment (old 'right' node)
        //   currentNode  → first node AFTER the reversed segment (right+1 node)
        //   sublistHead  → tail of the reversed segment (old 'left' node)

        // Reconnect: node before window → new front of reversed segment
        beforeLeft.next = previousNode;

        // Reconnect: old front (now tail) → first node after the window
        sublistHead.next = currentNode;

        return dummy.next; // Return real head (may have changed if left=1)
    }

    // ─── Demo ──────────────────────────────────────────────────────────────────
    public static void main(String[] args) {

        // Test 1: reverse middle segment
        LinkedListNode list1 = LinkedListNode.buildList(new int[]{1, 2, 3, 4, 5});
        System.out.print("Original:          ");
        LinkedListNode.printList(list1);
        LinkedListNode result1 = reverseSublist(list1, 2, 4);
        System.out.print("Reversed [2..4]:   ");
        LinkedListNode.printList(result1);

        // Test 2: reverse from the very start
        LinkedListNode list2 = LinkedListNode.buildList(new int[]{1, 2, 3, 4, 5});
        LinkedListNode result2 = reverseSublist(list2, 1, 3);
        System.out.print("Reversed [1..3]:   ");
        LinkedListNode.printList(result2);

        // Test 3: reverse entire list
        LinkedListNode list3 = LinkedListNode.buildList(new int[]{1, 2, 3, 4, 5});
        LinkedListNode result3 = reverseSublist(list3, 1, 5);
        System.out.print("Reversed [1..5]:   ");
        LinkedListNode.printList(result3);
    }
}
▶ Output
Original: 1 → 2 → 3 → 4 → 5 → null
Reversed [2..4]: 1 → 4 → 3 → 2 → 5 → null
Reversed [1..3]: 3 → 2 → 1 → 4 → 5 → null
Reversed [1..5]: 5 → 4 → 3 → 2 → 1 → null
⚠️
Pro Tip:The dummy node pattern is your best friend for linked list problems where the head might change. Instead of writing special-case logic for 'what if left equals 1', you give every node a predecessor by introducing a dummy whose next equals the real head. This alone eliminates a whole class of null-pointer bugs and makes your code cleaner to read on a whiteboard.
AspectIterative (3-Pointer)Recursive
Time ComplexityO(n)O(n)
Space ComplexityO(1) — no extra memoryO(n) — one stack frame per node
Risk on large listsNone — safe for 1M+ nodesStackOverflowError on very deep lists
ReadabilityExplicit steps, easy to traceElegant but requires stack intuition
Interview preferencePreferred for production contextOften asked to test recursion depth
Debugging easeEasy — step through in a debuggerHarder — call stack unwinds implicitly
Edge case handlingNaturally handles null and single nodeRequires explicit base case for both

🎯 Key Takeaways

  • You're reversing pointers, not values — swapping values is a shortcut that breaks on complex payloads and misses the point of the problem entirely.
  • In the iterative approach, the three-pointer sequence is strictly ordered: save next → reverse pointer → advance previous → advance current. Change that order and you lose your list.
  • Recursive reversal has O(n) space cost due to the call stack — always mention this trade-off in interviews, even if nobody asks. It's what separates a good answer from a great one.
  • The dummy node pattern eliminates the 'what if left equals 1' edge case in sublist reversal — it gives every node a guaranteed predecessor and keeps your code uniform.

⚠ Common Mistakes to Avoid

  • Mistake 1: Not saving the next pointer before reversing — Symptom: the rest of the list is permanently lost after the first iteration, and you end up with a list containing only one node. Fix: always do LinkedListNode nextNode = currentNode.next as the very first line inside the loop, before touching currentNode.next for any reason.
  • Mistake 2: Returning the wrong pointer after the iterative loop — Symptom: you return head (original head) instead of previousNode, so the caller gets a list that appears to have just one element (the old head, which now points to null). Fix: remember that when the while-loop exits, currentNode is null and previousNode is sitting on the new head. Return previousNode, not head.
  • Mistake 3: Forgetting to set head.next = null in the recursive solution — Symptom: the original head still points forward to its old next node after reversal, creating a cycle in the list. Printing the list will then spin in an infinite loop. Fix: always include head.next = null immediately after head.next.next = head in the recursive case — these two lines must always appear as a pair.

Interview Questions on This Topic

  • QCan you reverse a linked list in O(1) space? Walk me through your three-pointer approach step by step, and tell me what happens at each pointer on the second iteration for the list 1→2→3→4.
  • QWhat's the difference in space complexity between the iterative and recursive reversal, and when would the recursive approach actually cause a runtime failure in production?
  • QGiven the list 1→2→3→4→5, reverse only the nodes from position 2 to 4 without using any extra data structure. How do you reconnect the reversed segment back into the original list without losing the surrounding nodes?

Frequently Asked Questions

What is the easiest way to reverse a linked list in Java?

The iterative three-pointer approach is the easiest to implement correctly and the safest for production use. You maintain three references — previousNode, currentNode, and nextNode — and walk the list once, reversing each pointer as you go. It runs in O(n) time and uses O(1) space, making it the go-to choice in almost every situation.

Why can't I just swap the values in the nodes to reverse a linked list?

You technically can for integer lists, but it breaks the moment your nodes hold complex objects — swapping values would mean copying potentially large data payloads, and in some designs nodes don't expose their data publicly. More importantly, interviews and DSA problems test whether you understand pointer manipulation, not array-style swapping. Swapping values is considered the wrong answer even if it produces correct output for simple cases.

Will the recursive approach cause a StackOverflowError on large linked lists?

Yes, it can. Each recursive call consumes one stack frame, so a list with 10,000 nodes creates 10,000 frames. Java's default thread stack size typically allows around 500–1000 recursive calls before throwing a StackOverflowError, though this varies by JVM settings. For production code on lists of unknown size, always prefer the iterative approach. In interviews, mentioning this limitation proactively demonstrates strong engineering judgment.

🔥
TheCodeForge Editorial Team Verified Author

Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

← PreviousCircular Linked ListNext →Detect Loop in Linked List
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged