You're in a coding interview, and the interviewer asks you to reverse a linked list. Your mind goes blank. Sound familiar?
I've been there too. Early in my career, I bombed this exact question during a phone screen. The silence on the other end of the line felt like an eternity while I fumbled with pointers and nodes. But here's the thing: once you truly understand what's happening under the hood, reversing a linked list becomes one of those "aha" moments that clicks and never un-clicks.
This guide walks through everything you need to know about the reverse linked list algorithm—from the fundamentals of the singly linked list structure to the two primary solution strategies, complexity trade-offs, and even some advanced variations that show up in real interviews.
What is a Linked List and Why Reverse It?
Before we dive into the reversal logic, let's make sure we're on the same page about what a linked list actually is.
Understanding the Singly Linked List Structure
A singly linked list is a linear data structure where each element—called a node—contains two things: the data itself and a reference (or pointer) to the next node in the sequence.
[data: 1 | next] -> [data: 2 | next] -> [data: 3 | next] -> NULL
The first node is called the head, and the last node's next pointer points to NULL, marking the end of the list. Unlike arrays, linked lists don't give you random access to elements. To reach the third node, you must traverse from the head, following each next pointer one at a time. This is what we call linked list traversal.
The key thing to internalize: a linked list is defined by its pointers, not by its physical arrangement in memory. That's the insight that makes reversal possible in the first place.
Real-World Applications of List Reversal
You might be wondering—when would I actually need to reverse a linked list in production code?
- Browser history navigation: When you hit the "back" button, the browser traverses a history stack. Reversing the order of entries lets users navigate forward and backward through their session.
- Undo/redo functionality: Many text editors and design tools store operations in a linked structure. Reversing the list allows the system to replay or undo actions in the correct sequence.
- Palindrome detection: One elegant technique for checking if a linked list forms a palindrome involves finding the middle node, reversing the second half, and comparing both halves.
These aren't just academic exercises. I've personally implemented list reversal in a document versioning system where users needed to compare snapshots in reverse chronological order.
Iterative Approach: The Three-Pointer Technique
The iterative solution is the one most interviewers expect you to know cold. It's efficient, in-place, and demonstrates solid pointer manipulation skills.
Step-by-Step Algorithm Explanation
The core idea is deceptively simple: we walk through the list once, and for each node, we reverse its next pointer to point backward instead of forward. To do this without losing track of the rest of the list, we maintain three pointers:
prev: points to the node we've already processed (starts asNULL)curr: points to the node we're currently processing (starts at the head)next: temporarily storescurr.nextbefore we overwrite it
Here's the dance for each iteration:
- Save
curr.nextintonext(so we don't lose the rest of the list) - Point
curr.nexttoprev(this reverses the direction) - Shift
prevtocurrandcurrtonext
Let's trace through 1 -> 2 -> 3 -> NULL:
Initial: prev = NULL, curr = 1, next = NULL
Step 1: next = 2, 1.next = NULL, prev = 1, curr = 2
Step 2: next = 3, 2.next = 1, prev = 2, curr = 3
Step 3: next = NULL, 3.next = 2, prev = 3, curr = NULL
Result: NULL <- 1 <- 2 <- 3 (which is 3 -> 2 -> 1 -> NULL)
When curr becomes NULL, we've reached the end, and prev is pointing at the new head of the reversed list.
Code Implementation in C++, Java, and Python
Here's the iterative solution in three popular languages. I've added comments to highlight the critical lines.
C++:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
ListNode* next = nullptr;
while (curr != nullptr) {
next = curr->next; // Save the next node
curr->next = prev; // Reverse the pointer
prev = curr; // Move prev forward
curr = next; // Move curr forward
}
return prev; // prev is the new head
}
Java:
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
ListNode next = null;
while (curr != null) {
next = curr.next; // Save the next node
curr.next = prev; // Reverse the pointer
prev = curr; // Move prev forward
curr = next; // Move curr forward
}
return prev; // prev is the new head
}
Python:
def reverseList(head: ListNode) -> ListNode:
prev = None
curr = head
while curr:
next_temp = curr.next # Save the next node
curr.next = prev # Reverse the pointer
prev = curr # Move prev forward
curr = next_temp # Move curr forward
return prev # prev is the new head
Notice how the logic is identical across languages—only the syntax differs. Once you understand the pattern, you can implement it in any language.
Recursive Approach: A Deep Dive into the Call Stack
The recursive solution is more elegant to read but comes with a hidden cost. Let's explore both.
How Recursion Simplifies the Problem
Recursion leverages the fact that a linked list is itself a recursive structure: a list is either empty or consists of a head node followed by a smaller list. This self-similarity makes recursion a natural fit.
The base case is straightforward: if the list is empty or has only one node, it's already reversed. Return it as-is.
For the recursive case, we assume the sub-list starting at head.next has already been reversed. Our job is just to append the current node to the end of that reversed sub-list.
Here's the leap of faith: trust that the recursive call does its job. When it returns, head.next is now the last node of the reversed sub-list. So we set head.next.next = head to append the current node, then set head.next = NULL to terminate the new tail.
Recursive Code Example and Memory Analysis
Python:
def reverseList(head: ListNode) -> ListNode:
# Base case: empty list or single node
if not head or not head.next:
return head
# Reverse the rest of the list
new_head = reverseList(head.next)
# Append current node to the end of reversed sub-list
head.next.next = head
head.next = None
return new_head
The elegance is undeniable. But here's the trade-off: each recursive call adds a frame to the call stack. For a list with n nodes, that's n stack frames, giving us O(n) space complexity. The iterative version uses only a constant amount of extra space.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Iterative | O(n) | O(1) |
| Recursive | O(n) | O(n) |
| In my experience, the recursive solution is great for demonstrating conceptual understanding in an interview, but I'd think twice before using it in production code with large lists. A list with 10,000 nodes could easily cause a stack overflow depending on your environment's stack size. |
Iterative vs. Recursive: A Head-to-Head Comparison
Time and Space Complexity Analysis
Both approaches run in O(n) time—you must visit every node exactly once to update its pointer. The real difference lies in space.
The iterative approach is an in-place algorithm with O(1) space. It uses three pointers regardless of list length. This makes it the clear winner for production code, especially when dealing with large datasets.
The recursive approach, while more concise, requires O(n) space on the call stack. Each recursive call holds its own execution context, and these accumulate until the base case is reached. For most practical purposes, this is acceptable. But for extremely long lists, it's a genuine risk.
Which Approach Should You Use?
Here's my honest take after years of both writing production code and conducting interviews:
Choose iterative when:
- You're writing production code that needs to be robust and memory-efficient
- The list could be very large (thousands or millions of nodes)
- You're working in a language with limited stack depth
Choose recursive when:
- You're in an interview and want to demonstrate conceptual depth
- The list is known to be small
- You're working with functional programming paradigms where recursion is idiomatic
Some interviewers will specifically ask for one approach. Others will let you choose but probe your understanding of the trade-offs. Being fluent in both is the safest bet.
Mastering the LeetCode Problem (206)
Understanding the Problem Statement and Constraints
The LeetCode problem 206. Reverse Linked List is the canonical version of this question. The problem statement is refreshingly simple:
Given the
headof a singly linked list, reverse the list, and return the new head.
The constraints are:
- The number of nodes is in the range
[0, 5000] -5000 <= Node.val <= 5000
Nothing tricky about the constraints—no cycles, no special edge cases beyond the empty list and single-node list.
A Step-by-Step Solution Walkthrough
Let's apply the iterative solution to the LeetCode example: 1 -> 2 -> 3 -> 4 -> 5 -> NULL.
Edge cases first:
- Empty list (
head == NULL): returnNULL - Single node (
head.next == NULL): returnhead
Both are handled naturally by the iterative loop—if head is NULL or has no next, the loop doesn't execute, and we return prev, which is NULL or head respectively.
The accepted solution:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev = None
curr = head
while curr:
next_temp = curr.next
curr.next = prev
prev = curr
curr = next_temp
return prev
That's it. Fifteen lines of code, and you've solved a classic interview problem. The key is understanding why each line exists, not just memorizing the pattern.
Beyond the Basics: Variations and Advanced Challenges
Once you've mastered the basic reversal, you'll encounter variations that build on the same principles. These are worth knowing because they show up frequently in interviews at top companies.
Reversing a Sublist (LeetCode 92)
LeetCode 92. Reverse Linked List II asks you to reverse only a portion of the list, from position left to position right (1-indexed).
The strategy involves:
- Finding the node just before position
left(call itpre) - Reversing the sublist from
lefttorightusing the same three-pointer technique - Reconnecting the reversed sublist back to the rest of the list
The tricky part is handling edge cases like left = 1, where the reversal starts at the head. A common technique is using a dummy node that points to the head, which simplifies the reconnection logic.
Reversing Nodes in k-Group (LeetCode 25)
LeetCode 25. Reverse Nodes in k-Group is a step up in difficulty. You're given a list and an integer k, and you must reverse every group of k nodes. If the remaining nodes are fewer than k, leave them as-is.
This problem combines the basic reversal with careful group management. You can solve it iteratively by processing group by group, or recursively by reversing the first k nodes and then recursing on the remainder.
I've seen this problem asked at Google and Meta. It's a strong signal of whether a candidate truly understands pointer manipulation or just memorized the basic solution.
Frequently Asked Questions
Do you need 3 pointers to reverse a linked list?
No, you don't strictly need three pointers. The classic iterative method uses three (prev, curr, next) for clarity, but you can achieve the same result with just two pointers (prev and curr) by using a temporary variable to store curr.next before overwriting it. The three-pointer version is often taught because it makes each step explicit, but the two-pointer version is functionally identical.
Can you reverse a linked list with two pointers?
Yes. The two-pointer method uses prev and curr, plus a temporary variable to hold curr.next before reassigning it. Here's what that looks like:
def reverseList(head: ListNode) -> ListNode:
prev = None
curr = head
while curr:
temp = curr.next # Temporary variable
curr.next = prev
prev = curr
curr = temp
return prev
The "third pointer" in the three-pointer version is really just a named temporary variable. Whether you call it next or temp, the underlying logic is the same.
What is the time complexity of reversing a linked list?
The time complexity is O(n), where n is the number of nodes. This is because the algorithm must visit each node exactly once to update its next pointer. There's no way to reverse a linked list faster than linear time—you can't skip nodes since each one needs its pointer redirected.
Is reversing a linked list difficult?
It can be challenging at first, but not because the logic is complex. The difficulty comes from a mental shift: instead of thinking about the order of nodes, you need to think about the direction of pointers. Once you internalize that reversing a linked list is about redirecting pointers rather than rearranging nodes, the problem becomes straightforward. I've seen junior developers grasp it in an afternoon with good visual explanations.
Conclusion
Reversing a linked list is one of those foundational algorithms that keeps appearing in interviews and real-world applications. We've covered both main approaches:
- Iterative: Three pointers, O(n) time, O(1) space. The workhorse solution for production code.
- Recursive: Elegant and concise, O(n) time, O(n) space. Great for demonstrating conceptual understanding.
The real takeaway isn't memorizing either solution—it's understanding pointer manipulation at a fundamental level. Once you see that a linked list is defined by its pointers, not its node order, you can tackle any variation with confidence.
Now that you've mastered the basics, challenge yourself with the variations mentioned. Head over to LeetCode and try solving problems 206, 92, and 25. And if you found this guide helpful, share it with a friend who's also preparing for coding interviews!





