Reverse a Linked List
Reversing a linked list means redirecting every 'next' pointer to point backward. Three pointers — prev, curr, next — perform this in a single O(n) pass with O(1) space. The partial-reverse variant (reverse only positions m to n) extends this skeleton with a precise four-step wiring pattern.
Table of contents
- Before we start
- Picture this first (no code yet)
- First, the slow way (so you feel the pain)
- The turning point
- The one idea to remember
- The code, line by line
- Watch it happen, frame by frame
- Recursive version (for completeness)
- Variant: Reverse Linked List II — positions m to n (LC #92)
- Where to spot this pattern
- Common traps
- Check yourself
- Practice problems
Before we start
Reversing a linked list is the single most tested linked list operation in interviews. It also appears as a sub-step inside palindrome checking, reorder list, and k-group reversal. Get this so automatic that you can write it without thinking — then the harder problems become tractable. By the end you will be able to:
- Write the full reversal from memory, correctly, in under 60 seconds.
- Trace the three-pointer dance node by node on any example.
- Handle the partial reversal (reverse positions m to n) by extending the same skeleton.
Picture this first (no code yet)
A real-life story
Imagine a chain of dominoes, each one leaning against the next in a single direction. You want to make them all lean the other way — without knocking them over and starting a chaotic cascade.
You work on one domino at a time, from left to right. Before you tip the current domino to lean backward, you use one hand to hold the next domino in place (so you don't lose the rest of the chain), and the other hand to note which domino is now the "new front" (the one you just reversed). Then you move to the next domino and repeat.
Three hands (three pointers): one holding the previous position (prev), one at the current domino (curr), and one peeking at the next (nxt). That is the reversal algorithm.
First, the slow way (so you feel the pain)
You could copy all node values into an array, reverse the array, then copy values back. O(n) time — but O(n) space for the array. For a linked list of 10 million nodes, that is 40–80 MB of extra memory just to flip directions. The three-pointer approach uses O(1) extra space — just three pointer variables, no matter how long the list.
The turning point
Pause & think
You are at node B in the list A → B → C → D. You want to make B point back to A. Before you do B.next = A, what one thing must you save? If you don't save it, what is lost forever?
Answer
You must save B.next (which is C) before overwriting it. Once you do B.next = A, the pointer to C is gone — you've lost the rest of the list. That saved reference is the nxt pointer in the algorithm. Always save nxt = curr.next as the very first line inside the loop, before changing any pointers.
The one idea to remember
The entire pattern in one sentence
Save the next node, redirect current's pointer backward to prev, advance prev and curr — three pointer assignments per node, one pass: O(n) time, O(1) space.
The code, line by line
def reverseList(head):
prev = None # the node that current should now point to
curr = head # the node we are currently processing
while curr:
nxt = curr.next # ① save next (don't lose the rest of the list)
curr.next = prev # ② reverse the pointer
prev = curr # ③ advance prev
curr = nxt # ④ advance curr
return prev # curr is None; prev is the new head
Four lines inside the loop. Commit them as a sequence: save, reverse, advance-prev, advance-curr.
Watch it happen, frame by frame
List: 1 → 2 → 3 → 4 → 5
Initial: prev=None, curr=1
Iteration 1:
nxt = 2
curr.next = None → 1 → None
prev = 1
curr = 2
State: None ← 1 2 → 3 → 4 → 5
Iteration 2:
nxt = 3
curr.next = 1 → 2 → 1 → None
prev = 2
curr = 3
State: None ← 1 ← 2 3 → 4 → 5
Iteration 3:
nxt = 4
curr.next = 2 → 3 → 2 → 1 → None
prev = 3
curr = 4
Iteration 4:
nxt = 5
curr.next = 3
prev = 4
curr = 5
Iteration 5:
nxt = None
curr.next = 4
prev = 5
curr = None
While exits (curr = None).
Return prev = 5.
Final: 5 → 4 → 3 → 2 → 1 → None ✅
Recursive version (for completeness)
def reverseList_recursive(head):
if not head or not head.next:
return head # base case: 0 or 1 node
new_head = reverseList_recursive(head.next) # reverse the rest
head.next.next = head # the node after head should point back to head
head.next = None # head no longer points forward
return new_head # new head is unchanged throughout recursion
The recursive version uses O(n) stack space — avoid it for very long lists. Iterative is O(1) space and preferred in interviews.
Variant: Reverse Linked List II — positions m to n (LC #92)
Reverse only the nodes from position m to position n (1-indexed).
1 → 2 → 3 → 4 → 5, m=2, n=4 → 1 → 4 → 3 → 2 → 5
The approach: walk to position m−1 (call it tail_before), then perform a standard reversal for n − m + 1 nodes, then reconnect.
def reverseBetween(head, m: int, n: int):
dummy = ListNode(0) # dummy makes edge cases (m=1) uniform
dummy.next = head
prev = dummy
# Step 1: walk to the node just before position m
for _ in range(m - 1):
prev = prev.next
# prev is now at position m-1
# Step 2: reverse n-m+1 nodes starting at position m
curr = prev.next # this is the node at position m
for _ in range(n - m):
nxt = curr.next
curr.next = nxt.next # remove nxt from its current position
nxt.next = prev.next # wire nxt to the front of the reversed section
prev.next = nxt # update prev to point to nxt (new front)
return dummy.next
Frame-by-frame: 1 → 2 → 3 → 4 → 5, m=2, n=4
After walk: prev=node1, curr=node2
Iteration 1 (move node3 to front of sublist):
nxt = node3
curr.next = node4 → sublist so far: node2 → node4 → node5
nxt.next = node2 → node3 → node2 → node4 → node5
prev.next = node3 → node1 → node3 → node2 → node4 → node5
Iteration 2 (move node4 to front of sublist):
nxt = node4
curr.next = node5 → sublist: node2 → node5
nxt.next = node3 → node4 → node3 → node2 → node5
prev.next = node4 → node1 → node4 → node3 → node2 → node5
Done. Answer: 1 → 4 → 3 → 2 → 5 ✅
Pause & think
In the partial-reversal loop, curr never moves — only prev.next and nxt change. Why does curr stay fixed? What role does it play across all iterations?
Answer
curr starts at the node at position m. After each iteration, a new node is inserted before curr in the sublist — curr ends up at the tail of the growing reversed section. Since we are always inserting nodes at the front (between prev and curr), curr naturally drifts to the end of the reversed region. After all iterations, curr.next is the first node after the reversed section. curr is the "anchor" — the node that stays at the tail of the reversed portion, and its curr.next always points to whatever hasn't been reversed yet.
Where to spot this pattern
Trigger words:
- "reverse a linked list" (full or partial)
- "reverse nodes in k-group" (Variant: reverse every k nodes — LC #25)
- "palindrome linked list" — step 2 is a reversal
- "reorder list" — step 2 is a reversal of the second half
Common traps
Watch out for these
- Forgetting to save
nxtbefore overwritingcurr.next. This is the most common bug. Execution order must be:nxt = curr.nextFIRST, thencurr.next = prev. - Returning
currinstead ofprevat the end. After the loop,curris None (it walked off the end).previs the new head. Always returnprev. - Not using a dummy node for partial reversal with m=1. If
m=1, there is no "node before position m" — the head changes. A dummy node at position 0 unifies this edge case:prevstarts at the dummy. - Confusing the full-reverse three-pointer (
save nxt, flip curr.next, advance both) with the partial-reverse pattern (remove nxt from chain, wire to front, advance neither curr nor prev). They look similar but the partial-reverse loop body is different. Practise both separately.
Remember this forever
Full Reverse — 4 lines inside while:
prev, curr = None, head
while curr:
nxt = curr.next # ① save
curr.next = prev # ② flip
prev = curr # ③ advance prev
curr = nxt # ④ advance curr
return prev # new head
Partial Reverse (m to n) — insert at front:
# walk to position m-1 (prev), curr = node at m
for _ in range(n - m):
nxt = curr.next
curr.next = nxt.next # skip nxt in chain
nxt.next = prev.next # wire nxt to front
prev.next = nxt # update entry point
Trap: save nxt FIRST. Return prev, not curr.
Check yourself
After reversing `1 → 2 → 3 → 4 → 5`, what does the original `head` node (node 1) point to?
After the reversal, node 1's next pointer was set to None (it was prev = None when curr was on node 1). Node 1 is now the tail of the reversed list. So head.next = None, and head points to nothing — it is the last node. The variable head still holds a reference to node 1, but the new head of the list is prev (node 5). If you returned head instead of prev, you'd be returning the tail, not the head.
In the partial reversal, why do we use a dummy node?
The standard walk "go to position m−1" assumes a node at position m−1 exists. If m=1, position 0 doesn't exist in the original list. A dummy node at position 0 (pointing to head) gives prev a valid starting point even when m=1. After the reversal, dummy.next is the new head (which may have changed if m=1), and we return dummy.next rather than the possibly-stale head.
Practice problems
| Problem | Difficulty | What to notice | Link |
|---|---|---|---|
| Reverse Linked List | Easy | Four-line loop; return prev not curr | LC #206 |
| Reverse Linked List II | Medium | Dummy + walk + insert-at-front loop | LC #92 |
| Reverse Nodes in k-Group | Hard | Reverse every k nodes; chain the reversed groups | LC #25 |
| Palindrome Linked List | Easy | Middle → full reverse of second half → compare | LC #234 |
Next up: Merge Two Sorted Lists — the compare-heads, pick-smaller, advance-pointer dance that is the merge step in merge sort and the foundation for Merge K Sorted Lists.