Linked List Reversal Patterns
Master every linked list reversal pattern — iterative, recursive, reverse in groups of K, and reverse between positions m and n, with Python code and common pitfalls.
What you'll learn
- ✓Iterative reversal with the three-pointer technique
- ✓Recursive reversal and how the call stack unwinds
- ✓Reversing in groups of K nodes
- ✓Reversing between positions m and n (partial reversal)
- ✓Common mistakes and how to avoid them
- ✓When to pick iterative vs recursive
Prerequisites
- •Singly linked lists — see Linked Lists Intro
- •Recursion basics
Reversing a linked list is one of the most frequently asked interview questions. It appears as a standalone problem and as a building block inside harder problems (palindrome check, reverse in K-groups, reorder list). Let’s master every variant.
Pattern 1: Iterative Reversal
The iterative approach uses three pointers — prev, curr, and nxt — to reverse each link one at a time:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_iterative(head):
"""
Reverse the entire linked list iteratively.
Time: O(n), Space: O(1)
"""
prev = None
curr = head
while curr:
nxt = curr.next # 1. Save the next node
curr.next = prev # 2. Reverse the pointer
prev = curr # 3. Move prev forward
curr = nxt # 4. Move curr forward
return prev # prev is the new head
Step-by-step trace
For the list 1 -> 2 -> 3 -> 4 -> None:
| Step | prev | curr | nxt | List State |
|---|---|---|---|---|
| Init | None | 1 | - | 1→2→3→4→None |
| 1 | 1 | 2 | 2 | None←1 2→3→4→None |
| 2 | 2 | 3 | 3 | None←1←2 3→4→None |
| 3 | 3 | 4 | 4 | None←1←2←3 4→None |
| 4 | 4 | None | None | None←1←2←3←4 |
At the end, prev points to node 4 — the new head.
Common mistake: forgetting to save nxt
If you write curr.next = prev before saving curr.next, you lose the reference to the rest of the list. Always save nxt = curr.next first.
Pattern 2: Recursive Reversal
The recursive approach reverses the rest of the list first, then fixes the current node:
def reverse_recursive(head):
"""
Reverse the entire linked list recursively.
Time: O(n), Space: O(n) — call stack
"""
# Base case: empty or single node
if not head or not head.next:
return head
# Recurse on the rest
new_head = reverse_recursive(head.next)
# head.next is the last node of the reversed sublist
# Make it point back to head
head.next.next = head
head.next = None
return new_head
How the recursion unwinds
For 1 -> 2 -> 3 -> None:
reverse(1)
reverse(2)
reverse(3) → returns 3 (base case)
2.next.next = 2 → 3.next = 2
2.next = None → 3 -> 2 -> None
return 3
1.next.next = 1 → 2.next = 1
1.next = None → 3 -> 2 -> 1 -> None
return 3
The new head (3) bubbles up through every return.
Iterative vs recursive — when to choose
| Factor | Iterative | Recursive |
|---|---|---|
| Space | O(1) | O(n) call stack |
| Speed | Slightly faster | Stack overhead |
| Readability | Clear loop | Elegant but tricky |
| Stack overflow risk | None | Yes, for huge lists |
| Interview preference | Preferred | Good to know both |
Use iterative for production code — it is O(1) space and will not stack-overflow on large lists.
Pattern 3: Reverse Between Positions m and n
Reverse only the sublist from position m to position n (1-indexed), leaving the rest intact.
Example: 1 -> 2 -> 3 -> 4 -> 5, m=2, n=4 becomes 1 -> 4 -> 3 -> 2 -> 5.
def reverse_between(head, m, n):
"""
Reverse nodes from position m to n (1-indexed).
Time: O(n), Space: O(1)
"""
if not head or m == n:
return head
# Use a dummy node to handle edge case where m = 1
dummy = ListNode(0)
dummy.next = head
pre = dummy
# Move pre to the node just before position m
for _ in range(m - 1):
pre = pre.next
# Start reversing from position m
curr = pre.next
for _ in range(n - m):
# Pull the next node out and insert it after pre
nxt = curr.next
curr.next = nxt.next
nxt.next = pre.next
pre.next = nxt
return dummy.next
How the “pull and insert” works
Instead of reversing pointers, we repeatedly pull the next node and insert it right after pre:
Original: pre -> [2] -> 3 -> 4 -> 5 (m=2, n=4)
Iteration 1: Pull 3, insert after pre
pre -> [3] -> [2] -> 4 -> 5
Iteration 2: Pull 4, insert after pre
pre -> [4] -> [3] -> [2] -> 5
This avoids needing to reconnect the reversed segment to the list — the connections are maintained throughout.
Pattern 4: Reverse in Groups of K
Given a linked list, reverse every consecutive group of K nodes. If the last group has fewer than K nodes, leave it as-is (or reverse it, depending on the variant).
Example: 1 -> 2 -> 3 -> 4 -> 5, K=3 becomes 3 -> 2 -> 1 -> 4 -> 5.
def reverse_k_group(head, k):
"""
Reverse nodes in groups of k.
If remaining nodes < k, leave them unchanged.
Time: O(n), Space: O(1)
"""
# First check if there are at least k nodes remaining
count = 0
node = head
while node and count < k:
node = node.next
count += 1
if count < k:
return head # Not enough nodes — don't reverse
# Reverse k nodes
prev = None
curr = head
for _ in range(k):
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# head is now the tail of the reversed segment
# Connect it to the result of reversing the remaining list
head.next = reverse_k_group(curr, k)
return prev # prev is the new head of this segment
# Iterative version (avoids recursion stack)
def reverse_k_group_iterative(head, k):
"""
Reverse nodes in groups of k — fully iterative.
Time: O(n), Space: O(1)
"""
dummy = ListNode(0)
dummy.next = head
group_prev = dummy
while True:
# Check if k nodes exist
kth = group_prev
for _ in range(k):
kth = kth.next
if not kth:
return dummy.next # Done — fewer than k remaining
# Reverse k nodes starting from group_prev.next
group_next = kth.next # Save the node after this group
prev = group_next
curr = group_prev.next
for _ in range(k):
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# Connect the previous part to the reversed group
first_in_group = group_prev.next # This is now the tail
group_prev.next = prev # prev is now the head
group_prev = first_in_group # Move to end of reversed group
Testing all patterns
def to_list(head):
"""Convert linked list to Python list for easy viewing."""
result = []
while head:
result.append(head.val)
head = head.next
return result
def from_list(arr):
"""Create linked list from Python list."""
dummy = ListNode(0)
curr = dummy
for val in arr:
curr.next = ListNode(val)
curr = curr.next
return dummy.next
# Test iterative reversal
head = from_list([1, 2, 3, 4, 5])
print(to_list(reverse_iterative(head))) # [5, 4, 3, 2, 1]
# Test recursive reversal
head = from_list([1, 2, 3, 4, 5])
print(to_list(reverse_recursive(head))) # [5, 4, 3, 2, 1]
# Test reverse between m=2, n=4
head = from_list([1, 2, 3, 4, 5])
print(to_list(reverse_between(head, 2, 4))) # [1, 4, 3, 2, 5]
# Test reverse in K-groups (K=3)
head = from_list([1, 2, 3, 4, 5])
print(to_list(reverse_k_group(head, 3))) # [3, 2, 1, 4, 5]
# Test reverse in K-groups (K=2)
head = from_list([1, 2, 3, 4, 5])
print(to_list(reverse_k_group(head, 2))) # [2, 1, 4, 3, 5]
Common Mistakes
1. Losing the rest of the list
# WRONG — nxt not saved before overwriting curr.next
def reverse_wrong(head):
prev = None
curr = head
while curr:
curr.next = prev # Lost curr.next!
prev = curr
curr = curr.next # This is now prev, not the original next
return prev
2. Not using a dummy node for reverse_between
When m = 1, you are reversing from the head. Without a dummy node, you need special-case code. The dummy node eliminates this.
3. Off-by-one errors in K-group reversal
Make sure you count exactly K nodes before reversing. If you count K+1 or K-1, the groups will be wrong.
4. Forgetting to reconnect segments
After reversing a sublist, the old head of the sublist is now the tail. You must connect it to whatever comes after.
Reversal as a Building Block
Many harder problems use reversal internally:
def is_palindrome(head):
"""
Check if a linked list is a palindrome.
Uses: find middle + reverse second half + compare.
Time: O(n), Space: O(1)
"""
# Find middle
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
second_half = reverse_iterative(slow)
# Compare both halves
first = head
second = second_half
result = True
while second:
if first.val != second.val:
result = False
break
first = first.next
second = second.next
# Restore the list (optional but good practice)
reverse_iterative(second_half)
return result
def reorder_list(head):
"""
Reorder: L0→L1→...→Ln becomes L0→Ln→L1→Ln-1→...
Uses: find middle + reverse + merge.
Time: O(n), Space: O(1)
"""
if not head or not head.next:
return
# Find middle
slow, fast = head, head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
second = reverse_iterative(slow.next)
slow.next = None # Cut the list
# Merge alternating
first = head
while second:
tmp1 = first.next
tmp2 = second.next
first.next = second
second.next = tmp1
first = tmp1
second = tmp2
Complexity Summary
| Pattern | Time | Space |
|---|---|---|
| Iterative full reversal | O(n) | O(1) |
| Recursive full reversal | O(n) | O(n) |
| Reverse between m and n | O(n) | O(1) |
| Reverse K-group (recursive) | O(n) | O(n/k) |
| Reverse K-group (iterative) | O(n) | O(1) |
Practice Problems
- LeetCode 206 — Reverse Linked List: Full reversal, both iterative and recursive (Easy)
- LeetCode 92 — Reverse Linked List II: Reverse between positions m and n (Medium)
- LeetCode 25 — Reverse Nodes in k-Group: The hardest reversal problem (Hard)
- LeetCode 234 — Palindrome Linked List: Reverse + compare (Easy)
- LeetCode 143 — Reorder List: Find middle + reverse + merge (Medium)
- LeetCode 24 — Swap Nodes in Pairs: K-group with K=2 (Medium)
Key Takeaways
- The iterative three-pointer technique (
prev,curr,nxt) is the foundation — memorize it. - The “pull and insert” method for partial reversal avoids reconnection headaches.
- Always use a dummy node when the head might change.
- Reversal is a building block for palindrome checks, list reordering, and K-group problems.
- Prefer iterative over recursive in production — O(1) space, no stack overflow risk.
Related articles
- DSA Add Two Numbers as Linked Lists
Learn to add two numbers represented as linked lists — both reverse order (LeetCode 2) and forward order (LeetCode 445). Covers carry handling, different-length lists, and Python implementations with Big-O analysis.
- DSA Copy Linked List with Random Pointer
Learn two approaches to deep copy a linked list with random pointers — HashMap O(n) space and the interleaving O(1) space technique. Step-by-step walkthroughs with Python code and Big-O analysis.
- DSA Flatten a Multilevel Linked List
Learn to flatten a multilevel doubly linked list (LeetCode 430) and flatten sorted linked lists. Covers iterative and recursive DFS approaches with Python implementations and Big-O analysis.
- DSA Linked List Palindrome Check: Three Approaches
Learn three ways to check if a linked list is a palindrome — stack-based O(n) space, reverse-second-half O(1) space, and recursive. Step-by-step walkthroughs with Python code and Big-O analysis.