Reverse a Linked List: Iterative, Recursive & In-Place Explained
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.
// 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); } }
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.
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 } }
After reversal: 5 → 4 → 3 → 2 → 1 → null
Single node: 42 → null
Null list: null
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.
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)); } }
After reversal: 40 → 30 → 20 → 10 → null
Null input: null
Single node: 99 → null
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).
public class SublistReversal { /** * Reverses only the nodes between positions 'left' and 'right' (1-indexed). * Everything outside that range stays untouched. * * Example: list=1→2→3→4→5, left=2, right=4 → 1→4→3→2→5 * * 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); } }
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
| Aspect | Iterative (3-Pointer) | Recursive |
|---|---|---|
| Time Complexity | O(n) | O(n) |
| Space Complexity | O(1) — no extra memory | O(n) — one stack frame per node |
| Risk on large lists | None — safe for 1M+ nodes | StackOverflowError on very deep lists |
| Readability | Explicit steps, easy to trace | Elegant but requires stack intuition |
| Interview preference | Preferred for production context | Often asked to test recursion depth |
| Debugging ease | Easy — step through in a debugger | Harder — call stack unwinds implicitly |
| Edge case handling | Naturally handles null and single node | Requires 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.nextas 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 ofpreviousNode, 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,currentNodeis null andpreviousNodeis sitting on the new head. ReturnpreviousNode, nothead. - ✕Mistake 3: Forgetting to set
head.next = nullin 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 includehead.next = nullimmediately afterhead.next.next = headin 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.
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.