Reverse a Linked List: Iterative, Recursive & In-Place Explained
- 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.
- Core operation: pointer surgery, not data swapping — nodes stay in memory, only pointers change.
- Iterative approach: three pointers (previous, current, next) walk the list once — O(n) time, O(1) space.
- Recursive approach: trusts the call stack to reverse the tail, then stitches head back — O(n) time, O(n) space.
- Critical invariant: save next before reversing current's pointer, or you lose the rest of the list permanently.
- Production choice: always prefer iterative — recursive risks StackOverflowError on lists exceeding ~1000 nodes.
- Biggest mistake: returning head instead of previousNode after the loop exits — gives back a single-node list.
StackOverflowError in list reversal thread.
jcmd <pid> Thread.printjinfo -flag Xss <pid> (check thread stack size)Reversed list traversal enters infinite loop — CPU spikes to 100%.
jcmd <pid> Thread.printkill -3 <pid> (alternative thread dump)Memory leak — old list nodes not garbage collected after reversal.
jcmd <pid> GC.heap_dump /tmp/heap.hprofjmap -dump:format=b,file=/tmp/heap.hprof <pid>Production Incident
Production Debug GuideSymptom-driven investigation paths for production incidents.
Reversing a linked list is a pointer-manipulation primitive that underpins browser history navigation, undo-redo stacks, and network packet reordering pipelines. It is not a toy problem — it is a litmus test for whether you understand how references work under the hood.
The core challenge: a singly linked list is unidirectional. Each node only knows its successor. To reverse it, you must build backward connections while walking forward, because once you advance past a node without saving a reference, it is unreachable. This constraint forces precise pointer choreography.
Two canonical solutions exist: iterative (three-pointer loop) and recursive (call-stack unwinding). The iterative approach is production-safe with O(1) space. The recursive approach is elegant but carries O(n) stack depth — a real failure mode on large inputs.
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.
package io.thecodeforge.list; /** * Generic singly linked list node. * Used as the building block for all reversal examples. */ 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); } }
- You cannot see the whole list at once — you only have local references.
- Every pointer you reverse destroys one forward connection permanently.
- The only safety net is saving the next pointer before you overwrite it.
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.
package io.thecodeforge.list; /** * Iterative in-place linked list reversal. * Production-safe: O(n) time, O(1) space, no stack depth risk. */ 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.
package io.thecodeforge.list; /** * Recursive linked list reversal. * Elegant but carries O(n) stack depth — use only on bounded input. */ public class RecursiveReversal { private static final int MAX_SAFE_DEPTH = 5000; /** * 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) { return reverseRecursive(head, 0); } /** * Overloaded version with depth guard for production safety. */ private static LinkedListNode reverseRecursive(LinkedListNode head, int depth) { // Depth guard: fail fast instead of letting the JVM crash if (depth > MAX_SAFE_DEPTH) { throw new IllegalStateException( "Recursion depth exceeded " + MAX_SAFE_DEPTH + ". " + "Use iterative reversal for large lists."); } // 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, depth + 1); // 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
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).
package io.thecodeforge.list; /** * Reverses a sublist between positions left and right (1-indexed). * Surrounding nodes remain untouched. */ 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 |
| Recoverability on failure | Loop can be interrupted safely | StackOverflowError kills the thread |
| Testability | Trivial — input/output, no state | Requires stack depth mocking for bounds tests |
🎯 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
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?
- QHow would you modify the recursive reversal to fail gracefully instead of throwing StackOverflowError? What is the maximum safe recursion depth for a typical JVM with 1MB thread stack?
- QIf your linked list nodes contain complex objects (protobuf messages, large byte buffers), why is pointer reversal strictly superior to value-swapping reversal? What are the memory and performance implications?
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.
How do I reverse only part of a linked list?
Use the sublist reversal technique: navigate to the node before the reversal window, run the standard three-pointer reversal for exactly (right - left + 1) steps, then reconnect the four boundary pointers. A dummy node before the head simplifies the case where the reversal starts at position 1.
Is there a way to detect if a reversal introduced a cycle?
After reversal, run Floyd's cycle detection algorithm (fast/slow pointers). If the fast pointer ever meets the slow pointer, a cycle exists. Alternatively, verify that the last node's next is null. In production, add an invariant check in debug builds that asserts list integrity after every mutation.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.