Reverse a Doubly Linked List: Iterative, Recursive, and the Common Mistakes
Reversing a doubly linked list (DLL) is a classic interview question that tests pointer manipulation under more constraints than the singly-linked variant. Each node has both prev and next pointers, and a correct reversal swaps both pointers at every node while updating the head reference. The base problem is simple in concept but easy to get wrong under pressure — most candidates trip on either pointer order or the head update. This guide covers the iterative and recursive approaches with working code and the mistakes that show up in interviews.
Problem Statement
Given the head of a doubly linked list, reverse it in place and return the new head. Each node has val, prev, and next; the original head becomes the new tail.
Examples:
- Input:
1 ⇄ 2 ⇄ 3 ⇄ 4→ Output:4 ⇄ 3 ⇄ 2 ⇄ 1 - Input: empty list → Output: empty list
- Input: single node → unchanged
Approach 1: Iterative Pointer Swap (Standard Answer)
Walk the list, swapping prev and next at each node. The “next” node before the swap becomes the next iteration’s target. After processing the last node, return it as the new head.
class DLLNode:
def __init__(self, val=0, prev=None, next=None):
self.val = val
self.prev = prev
self.next = next
def reverse_dll(head: DLLNode) -> DLLNode:
"""O(n) time, O(1) space."""
current = head
new_head = head
while current:
# Swap prev and next pointers
current.prev, current.next = current.next, current.prev
new_head = current
# Advance: original next is now in current.prev
current = current.prev
return new_head
# Test helper to print the list forward and backward
def to_forward(head):
result = []
while head:
result.append(head.val)
if not head.next:
tail = head
head = head.next
return result, tail
def to_backward(tail):
result = []
while tail:
result.append(tail.val)
tail = tail.prev
return result
# Build 1 ⇄ 2 ⇄ 3 ⇄ 4
nodes = [DLLNode(i) for i in range(1, 5)]
for i in range(3):
nodes[i].next = nodes[i+1]
nodes[i+1].prev = nodes[i]
new_head = reverse_dll(nodes[0])
fwd, tail = to_forward(new_head)
bwd = to_backward(tail)
print(fwd) # [4, 3, 2, 1]
print(bwd) # [1, 2, 3, 4]
Complexity: O(n) time, O(1) extra space.
Why the swap-then-advance pattern works: after we swap prev and next on the current node, the original next direction is preserved in current.prev (since we swapped). So we advance via current.prev, which is the original next.
Approach 2: Recursive Reversal
Recurse to the end, then unwind, swapping pointers as the call stack unwinds. O(n) stack space; useful when the interviewer asks for a recursive variant.
def reverse_dll_recursive(head: DLLNode) -> DLLNode:
"""O(n) time, O(n) stack space."""
if not head:
return None
# Swap this node's pointers
head.prev, head.next = head.next, head.prev
# If head.prev (original next) is None, we're at the new head
if not head.prev:
return head
# Recurse on the original next (now in head.prev)
return reverse_dll_recursive(head.prev)
Same time complexity; uses recursion stack space proportional to list length. For very long lists, prefer the iterative version.
Approach 3: Two-Pointer Walk (Alternative)
Use a temporary variable to remember the original next, then swap and advance. Conceptually equivalent to Approach 1 but spelled out more explicitly.
def reverse_dll_explicit(head: DLLNode) -> DLLNode:
"""O(n) time, O(1) space — explicit temp variable."""
current = head
new_head = None
while current:
# Save original next
next_node = current.next
# Swap prev and next
current.next = current.prev
current.prev = next_node
# Track the new head (the last node we processed)
new_head = current
# Advance to the original next
current = next_node
return new_head
Some find this version easier to reason about because the temp variable makes the original next explicit. Pick whichever style feels cleaner; both are O(n) time and O(1) space.
Common Mistakes
- Forgetting to update both pointers. Reversing only
nextwhile leavingprevalone produces a broken list — backwards traversal fails. Always swap both at every node. - Losing track of the new head. The new head is the last node visited (the original tail). Track it explicitly with a variable; don’t try to compute it after the loop.
- Advancing in the wrong direction. After swapping, the original
nextdirection is incurrent.prev. Advancing viacurrent.nextgoes backward — toward already-processed nodes — and the loop terminates incorrectly. - Returning the original head. The original head becomes the new tail. Return the new head (last node visited), not
head. - Not handling empty list and single-node cases. The standard implementation handles these via the loop condition (empty: loop body doesn’t execute; single node: one swap, no advance), but bugs in the head-tracking can manifest in these edge cases. Test them.
- Memory issues with mutual references. In garbage-collected languages, mutual
prev/nextreferences aren’t a concern. In manual-memory languages (C, C++ without smart pointers), the reversal mutates pointers in place — no allocation needed, no leaks possible from the algorithm itself.
Common Variations
Reverse a singly linked list
(LeetCode #206) The simpler variant. Only one pointer per node; same iterative pattern with one swap per node.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_singly(head: ListNode) -> ListNode:
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prev
Reverse in groups of k
(LeetCode #25 — Reverse Nodes in k-Group) Reverse the first k nodes, then the next k, and so on. Apply the standard reversal to each group, stitching them together.
Reverse between positions m and n
(LeetCode #92) Reverse a sub-segment of the list. Walk to position m, reverse n-m nodes, reconnect to the surrounding nodes.
Check if a linked list is a palindrome
(LeetCode #234) Uses the singly-linked reversal as a subroutine: reverse the second half, compare to first half. See our palindrome guide.
Detect and remove cycles
If the DLL has a cycle (some node’s next points back to an earlier node), the reversal algorithm above doesn’t terminate. Detect cycles first via Floyd’s algorithm, or assume well-formed input.
Frequently Asked Questions
What’s the expected interview answer?
Iterative pointer swap, O(n) time and O(1) space. Walk through the algorithm: at each node, swap prev and next; advance via the new prev (which was the original next). Return the last visited node as the new head. Mention the recursive variant as an alternative; some interviewers explicitly want the iterative version.
Why is reversing a DLL different from reversing a singly linked list?
Both swap pointer directions, but the DLL has two pointers per node. The algorithm is structurally similar; the work per node doubles. The DLL version is conceptually slightly easier because the “where do I go next” answer is right there in current.prev after the swap — no separate tracking needed. The singly-linked version requires explicitly saving next before overwriting current.next.
How do I keep track of the new head?
Maintain a variable that holds the last visited node. At the end of the loop, that variable is the new head. Alternative: notice that when current.prev (the new “next direction”) becomes None, you’re at the new head — return it directly. Both work; the explicit variable is clearer for most candidates.
What if the doubly linked list is circular?
The standard algorithm doesn’t terminate on circular DLLs because the loop never reaches None. For circular DLLs, you’d track the original head and stop when you return to it. This is a different problem; clarify with the interviewer whether the input is circular before assuming.
How does this generalize to other data structures?
The “swap pointers and advance” pattern applies to any structure with bidirectional links: doubly linked lists, sequences in graphs, certain tree representations. The key insight — using one pointer to remember “where to go next” while you’re rewriting another — is reusable. Mastering DLL reversal often unlocks other linked-structure problems.
See also: Check if a Linked List is a Palindrome • Detect a Cycle in a Linked List • Serialize and Deserialize a Binary Tree
💡Strategies for Solving This Problem
The Swap Pattern
Got this at Meta. It's simpler than reversing a singly linked list in some ways - you have both prev and next pointers to work with.
The Problem
Reverse the direction of all pointers in a doubly linked list. What was next becomes prev, what was prev becomes next.
Example:
Original: 1 <-> 2 <-> 3 <-> 4 Reversed: 4 <-> 3 <-> 2 <-> 1
Key Insight
For each node, swap its prev and next pointers. That's it. No need to track previous node like in singly linked list.
The Algorithm
- Start at head
- For each node: swap node.prev and node.next
- Move to next node (which is now in node.prev!)
- Return what was the last node (now the new head)
O(n) time, O(1) space. Single pass through the list.
The Tricky Part
After swapping pointers, "next" is now in the prev field. You need to move to node.prev to go forward. This confuses people.
Alternative: Three Pointer Approach
Keep track of prev, current, and next. Similar to singly linked list reversal but swap both directions. More verbose but maybe clearer.
At Meta
My interviewer asked "How is this different from reversing a singly linked list?" The key difference: you don't need to track the previous node separately because each node already has a prev pointer.
Then asked about edge cases: empty list, single node, two nodes. All work with the same code.