In-place Linked List Reversal
Interviewer cue: “reverse this linked list”, “reverse it between position
left and right”, or “reverse every group of k nodes” — all three are
the same trick applied to a different slice of the list. No new nodes are
ever allocated; you just rewire existing next pointers, one at a time.
What you’ll learn
Section titled “What you’ll learn”- The reusable prev / curr / next three-pointer template.
- How to reverse only a sublist (positions
leftthroughright), not the whole list. - How to extend that into reversing in groups of
k, recursively. - Why every variant stays time and extra space.
The cue
Section titled “The cue”The pattern: three pointers, one pass
Section titled “The pattern: three pointers, one pass”Whole-list reversal is the base case every variant below builds on: walk the
list once, and at each node flip its next pointer to point backward
instead of forward.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build(values):
dummy = ListNode()
tail = dummy
for v in values:
tail.next = ListNode(v)
tail = tail.next
return dummy.next
def to_list(head):
out = []
while head:
out.append(head.val)
head = head.next
return out
def reverse_list(head):
prev = None
curr = head
while curr:
nxt = curr.next # save the rest of the list before overwriting it
curr.next = prev # 1. flip this node's pointer backward
prev = curr # 2. prev catches up to curr
curr = nxt # 3. curr advances to the node we saved
return prev # prev ends up as the new head
head = build([1, 2, 3, 4, 5])
print("before:", to_list(head))
print("after: ", to_list(reverse_list(head)))Visual intuition
Section titled “Visual intuition”Four assignments per node, and their order is the entire problem. Step
through one iteration at a time and watch what nxt is protecting:
Pause on the 'save' frame. If cur.next were flipped before nxt was saved, everything to the right would become unreachable in one assignment. That is the whole reason the reversal needs three pointers rather than two.
How it works: reversing only a slice
Section titled “How it works: reversing only a slice”The same pointer dance works on any contiguous slice of the list. Walk to
one node before the slice, then repeatedly move the node right after prev
to the front of the slice — this is the “head-insertion” trick behind
LeetCode’s Reverse Linked List II.
graph LR
N1["1 (prev)"] --> N2["2 (curr)"]
N2 --> N3["3"]
N3 --> N4["4"]
N4 --> N5["5"]
graph LR
N1["1"] --> N4["4"]
N4 --> N3["3"]
N3 --> N2["2"]
N2 --> N5["5"]
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build(values):
dummy = ListNode()
tail = dummy
for v in values:
tail.next = ListNode(v)
tail = tail.next
return dummy.next
def to_list(head):
out = []
while head:
out.append(head.val)
head = head.next
return out
def reverse_between(head, left, right):
dummy = ListNode(0, head)
prev = dummy
for _ in range(left - 1):
prev = prev.next # walk to the node just before the sublist
curr = prev.next # first node of the sublist -- never moves
for _ in range(right - left):
moved = curr.next
curr.next = moved.next
moved.next = prev.next
prev.next = moved # each iteration pulls one node to the front of the sublist
return dummy.next
head = build([1, 2, 3, 4, 5])
print("before:", to_list(head))
print("after: ", to_list(reverse_between(head, 2, 4)))Worked example: reversing in groups of k
Section titled “Worked example: reversing in groups of k”Reverse Nodes in k-Group repeats the whole-list reversal on fixed-size
chunks: check that k nodes remain, reverse exactly that many, then recurse
on the rest.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build(values):
dummy = ListNode()
tail = dummy
for v in values:
tail.next = ListNode(v)
tail = tail.next
return dummy.next
def to_list(head):
out = []
while head:
out.append(head.val)
head = head.next
return out
def reverse_k_group(head, k):
node = head
count = 0
while node and count < k: # check that at least k nodes remain
node = node.next
count += 1
if count < k:
return head # fewer than k left: leave this tail as-is
prev = None
curr = head
for _ in range(k): # reverse exactly this group of k
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
head.next = reverse_k_group(curr, k) # curr is now the head of the remaining list
return prev # prev is the new head of this reversed group
head = build([1, 2, 3, 4, 5])
print("before: ", to_list(head))
print("k=2 reversed:", to_list(reverse_k_group(head, 2)))Dry run
Section titled “Dry run”Whole-list reversal — 1 → 2 → 3 → 4. Each row is one iteration of the four-line loop:
curr | prev | nxt (saved first) | list built so far |
|---|---|---|---|
| 1 | None | 2 | 1 |
| 2 | 1 | 3 | 2 → 1 |
| 3 | 2 | 4 | 3 → 2 → 1 |
| 4 | 3 | None | 4 → 3 → 2 → 1 |
Loop ends when curr is None; prev is the new head — returning curr returns None, which
is the single most common slip here.
nxt = curr.nextmust come first. The very next line overwritescurr.next, so without the save the rest of the list is unreachable and the function returns a one-node list. Everything else in the loop is order-insensitive; this one line is not.- The reversed prefix grows behind
prev, and the untouched suffix hangs offnxt. At every step the list is temporarily two lists — that invariant is what makes the loop easy to verify by hand. prevstarts atNone, and that is what terminates the reversed list. The original head becomes the tail, and itsnextmust beNone; seedingprev = headinstead creates a cycle.
Sublist reversal — reverse_between([1,2,3,4,5], 2, 4). A different technique: rather than
flipping pointers, repeatedly pull the node after curr to the front of the sublist. prev
lands on node 1, curr on node 2 and never moves.
| iteration | node pulled forward | whole list afterwards |
|---|---|---|
| 1 | 3 | 1 → 3 → 2 → 4 → 5 |
| 2 | 4 | 1 → 4 → 3 → 2 → 5 |
Answer 1 → 4 → 3 → 2 → 5, in right - left = 2 iterations.
currstays put and drifts backwards through the sublist. It began as the sublist’s head and ends as its tail, which is why it needs no updating — it is the node everything else moves in front of.- The three assignments must run in that order: detach
moved(curr.next = moved.next), point it at the current sublist head (moved.next = prev.next), then letprevadopt it. Swap the last two andmoved.nextpoints at itself. - A dummy node removes the
left == 1special case, exactly as on the dummy-head page — otherwise reversing from position 1 has to updateheadseparately.
Complexity
Section titled “Complexity”Every variant above is time — each node’s next pointer is touched a
constant number of times — and extra space, since nothing beyond
a handful of pointers (prev, curr, nxt) is ever allocated. (k-group
reversal’s recursion adds call-stack frames; an iterative version
gets back to true space if that matters.)
When to use it
Section titled “When to use it”Reach for this pattern whenever the interviewer says “reverse” and a linked
list is involved — whole list, a [left, right] slice, or fixed-size
groups. It’s also the core subroutine inside trickier problems like
palindrome-linked-list checks (reverse the second half, then compare) and
reordering a list around its middle.
The variant map
Section titled “The variant map”| Problem | What changes | The catch |
|---|---|---|
| LC 206 Reverse Linked List | the base loop | return prev, not curr |
| LC 92 Reverse Linked List II | reverse only [left, right] | use a dummy so left == 1 needs no special case; curr never moves |
| LC 25 Reverse Nodes in k-Group | reverse each full group, leave a short tail alone | count k nodes before reversing — LC 24 is this with k = 2 |
| LC 24 Swap Nodes in Pairs | k = 2 | often written directly with prev/first/second naming |
| LC 234 Palindrome Linked List | find the middle, reverse the second half, compare | politeness: restore the list afterwards if the caller keeps it |
| LC 143 Reorder List | reverse the second half, then interleave | see Copy Flatten and Reorder |
| LC 445 Add Two Numbers II | reverse both, add with carry, reverse the result | or use two stacks and skip the mutation entirely |
| LC 2074 Reverse Even Length Groups | group sizes grow 1, 2, 3, … | reverse a group only when its actual length is even, which the tail may not be |
| Doubly linked list | swap each node’s prev and next | one loop, no third pointer needed |
| Reverse by value rather than by pointer | copy values into a list, write them back reversed | space; legitimate when node identity must be preserved |
Interview follow-ups
Section titled “Interview follow-ups”| They ask | What they’re checking | The answer |
|---|---|---|
“Why save nxt before flipping?” | Whether you understand the hazard | Because curr.next = prev destroys the only reference to the rest of the list. Save first, flip second — every other line in the loop is order-insensitive, that one is not |
| “What do you return?” | The classic slip | prev. When the loop exits, curr is None and prev is the last node visited, i.e. the new head |
| “Do it recursively” | Breadth | reverse(head.next) then wire head.next.next = head; head.next = None. Same time but stack, so on a -node list CPython raises RecursionError — mention that, because it makes the iterative version the better answer rather than just an alternative |
“Reverse only positions left to right” | Whether you can adapt it | Walk a dummy-anchored prev to left - 1, then pull the node after curr to the front of the sublist right - left times. curr stays put and becomes the sublist’s tail |
“Groups of k, and the last group may be short” | Reading carefully | Count k nodes ahead before reversing anything; if fewer than k remain, leave that group as-is. Reversing first and discovering the shortfall afterwards is the standard LC 25 bug |
| “Check whether the list is a palindrome in space” | Composition | Fast/slow to the middle, reverse the second half, compare in step, and restore if the caller still needs the list. Anything else costs space |
| “Is your solution destructive?” | Engineering judgement | Yes — the caller’s list is rewired. Say it unprompted, and offer the value-copy version ( space) when node identity or the original order matters |
| “Doubly linked list instead” | Generality | Swap each node’s prev and next in one pass; no third pointer is needed because the backward link already holds what nxt was saving |
Practice — real LeetCode problems
Section titled “Practice — real LeetCode problems”Each exercise is the actual LeetCode problem with its real method signature and LeetCode’s own examples as the test. Write the body, press Run, and match the expected output.
LC 92 — Reverse Linked List II · Medium
Section titled “LC 92 — Reverse Linked List II · Medium”Problem. Reverse the nodes from position left to position right
(1-indexed, inclusive) and return the head. Do it in one pass.
Constraints. 1 <= n <= 500, 1 <= left <= right <= n.
Examples. [1,2,3,4,5], left = 2, right = 4 gives [1,4,3,2,5] ·
[5], left = 1, right = 1 gives [5]
Editorial
The head-insertion formulation is what makes this a genuine one-pass solution:
rather than reversing the segment and then reattaching both ends, you repeatedly lift
the node after curr and splice it to the front of the segment. The boundaries stay
correct throughout, so there is nothing to fix up afterwards.
Time , one pass. Space .
Two things carry it:
- The dummy head.
left = 1means the segment starts at the real head, which would otherwise need its own branch.([1,2], 1, 2)and([1,2,3], 1, 3)cover that case. currnever moves. It begins as the segment’s first node and ends as its last, which is correct after a reversal. Trying to advancecurris the usual mistake.
The loop runs right - left times, not right - left + 1: reversing a k-node
segment needs k - 1 relocations. ([5], 1, 1) runs zero iterations and returns the
list unchanged.
Follow-ups: “Reverse in groups of k (LC 25)?” — next problem: the same inner
loop, repeated. “Two passes instead?” — detach the segment, reverse it with
LC 206, reattach; easier to
explain, more pointer bookkeeping. “Why does curr become the tail?” — everything
inserted goes in front of it, so it is pushed to the back of the segment.
LC 25 — Reverse Nodes in k-Group · Hard
Section titled “LC 25 — Reverse Nodes in k-Group · Hard”Problem. Reverse the nodes of the list k at a time. If the final group has
fewer than k nodes, leave it as is. You may not alter the node values.
Constraints. 1 <= k <= n <= 5000.
Examples. [1,2,3,4,5], k = 2 gives [2,1,4,3,5] ·
[1,2,3,4,5], k = 3 gives [3,2,1,4,5] · [1,2], k = 3 gives [1,2]
Editorial
This is LC 92’s inner loop applied repeatedly, with one structural addition: knowing when to stop.
Time — one counting pass plus one reversing pass. Space .
Counting the length first makes the “leave a short final group alone” rule fall out
naturally: the while remaining >= k condition simply stops, leaving the tail
untouched. The alternative — looking ahead k nodes before each group — also works
and avoids the counting pass, but needs care to restore position when the lookahead
fails.
prev = curr after each group is the line worth understanding. Because head insertion
pushes curr to the back of its group, curr ends up as the group’s tail — which
is exactly the node that must precede the next group.
The three edge cases in the tests: k = 1 returns the list unchanged (zero
relocations per group), a list shorter than k is untouched, and an uneven split
leaves the remainder in order.
Follow-ups: “Reverse the short final group too?” — drop the length guard and
reverse whatever remains. “Without counting first?” — look ahead k nodes per group
and bail out if you cannot. “Recursively?” — reverse the first group, then recurse on
the rest and attach; stack. “Why is this Hard?” — the group-boundary
bookkeeping, not the reversal itself.
LC 234 — Palindrome Linked List · Easy
Section titled “LC 234 — Palindrome Linked List · Easy”Problem. Return True if the linked list reads the same forwards and
backwards. Aim for time and space.
Constraints. 1 <= n <= 10^5, 0 <= Node.val <= 9.
Examples. [1,2,2,1] gives True · [1,2] gives False ·
[1,2,1] gives True
Editorial
A composition of three techniques from this phase: fast/slow pointers to find the middle, in-place reversal of the second half, then a straight comparison.
Time . Space .
Looping while right rather than while left and right is the detail that handles
odd lengths. With [1,2,1], the reversed second half is [1,2] and the first half is
[1,2,1] — the shared middle element compares against itself harmlessly, and the loop
ends when the shorter reversed half is exhausted.
The -space alternative is much easier and worth offering first: copy the values into a list and compare it with its reverse. The version is the improvement being asked for.
Follow-ups: “Restore the list?” — reverse the second half back before returning.
” space version?” — values into a list, compare with [::-1]. “Recursively?”
— possible with a helper returning from the tail, but stack. “Why not compare
from both ends directly?” — a singly linked list cannot be walked backwards.
LeetCode problem set
Section titled “LeetCode problem set”Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.
Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.
- 206Reverse Linked ListeasyThe whole-list `prev/curr/next` template
- 24Swap Nodes in PairsmediumK-group reversal with `k = 2`
- 92Reverse Linked List IImediumReverse only the `[left, right]` slice
- 25Reverse Nodes in k-GrouphardReverse fixed-size chunks, recursively
Self-check
Section titled “Self-check”-
Why must `nxt = curr.next` come before `curr.next = prev`?
Every other line in the four-line loop is order-insensitive; this one is not. 'Save what you are about to overwrite' is the whole discipline of pointer surgery.
pch.quizShowAnswer
B — Because flipping the pointer destroys the only reference to the rest of the list — without the save the function returns a one-node list — Every other line in the four-line loop is order-insensitive; this one is not. 'Save what you are about to overwrite' is the whole discipline of pointer surgery.
-
What does the loop return, and why is that a common mistake?
Returning curr returns None. Returning head returns a one-node list (the old head is now the tail). The new head is always prev.
pch.quizShowAnswer
B — `prev` — when the loop exits `curr` is None, and `prev` is the final node, i.e. the new head — Returning curr returns None. Returning head returns a one-node list (the old head is now the tail). The new head is always prev.
-
In sublist reversal (LC 92), why does `curr` never move?
That is why this variant uses a different technique from whole-list reversal — repeatedly pulling the next node to the front, rather than flipping each pointer in turn.
pch.quizShowAnswer
B — Because it is the sublist's original head: every other node in the sublist is pulled in front of it, so it drifts backwards and ends as the sublist's tail — That is why this variant uses a different technique from whole-list reversal — repeatedly pulling the next node to the front, rather than flipping each pointer in turn.
-
The recursive reversal is elegant. Why prefer the iterative version?
Naming the concrete failure — the recursion limit at LeetCode-scale input — is what turns 'I prefer iterative' into a technical reason.
pch.quizShowAnswer
B — Same O(n) time but O(1) space instead of O(n) stack — a 10^5-node list raises RecursionError in CPython — Naming the concrete failure — the recursion limit at LeetCode-scale input — is what turns 'I prefer iterative' into a technical reason.
-
LC 25 reverses in groups of k, leaving a short final group untouched. What is the standard bug?
Once you have reversed a partial group you have to undo it, which is far more code than the look-ahead check. LC 24 is the same problem with k = 2.
pch.quizShowAnswer
B — Reversing first and discovering afterwards that fewer than k nodes remained — count k nodes ahead BEFORE touching any pointers — Once you have reversed a partial group you have to undo it, which is far more code than the look-ahead check. LC 24 is the same problem with k = 2.
-
Your reversal passes the tests. What should you volunteer about it?
Some interviewers care a great deal about mutating an argument. Raising it yourself is cheap and reads as engineering judgement rather than a gap.
pch.quizShowAnswer
B — That it is destructive — the caller's list is rewired — and that a value-copy version costs O(n) space but preserves the original — Some interviewers care a great deal about mutating an argument. Raising it yourself is cheap and reads as engineering judgement rather than a gap.
Recall card
Section titled “Recall card”- Cue — “reverse” a list, a
[left, right]slice, or groups ofk; or an -space follow-up on any list problem. - The loop, four lines —
nxt = curr.next→curr.next = prev→prev = curr→curr = nxt. Save before flipping. - Return
prev, nevercurr(which isNone) and neverhead(now the tail). prev = Noneat the start is what terminates the reversed list; seeding it withheadmakes a cycle.- Sublist (LC 92) — dummy, walk
prevtoleft - 1, then pull the node aftercurrto the frontright - lefttimes.currnever moves. - Groups of
k(LC 25) — countkahead before reversing; leave a short tail alone. - Cost — time, space. Recursive is stack and dies at ~1000 frames.
- It is destructive — say so, and offer the value-copy alternative when identity matters.
- Every reversal variant is the same idea: walk once, flip
nextpointers backward, never allocate a new node. - Reversing a slice uses a dummy node plus repeated “pull the next node to the front of the slice” moves.
- k-group reversal first checks that a full group remains, then reverses
exactly
knodes and recurses on the rest. - All variants: time, extra space.
Next: Breadth First Search — the queue-based traversal pattern behind shortest paths and level-order processing.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading