In-place Linked List Reversal
Interviewer cue: “reverse this linked list”, “reverse it between position
leftleft and rightright”, or “reverse every group of kk 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 nextnext pointers, one at a time.
What you’ll learn
- The reusable prev / curr / next three-pointer template.
- How to reverse only a sublist (positions
leftleftthroughrightright), not the whole list. - How to extend that into reversing in groups of
kk, recursively. - Why every variant stays time and extra space.
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 nextnext 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)))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)))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 prevprev
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)))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
Reverse Nodes in k-Group repeats the whole-list reversal on fixed-size
chunks: check that kk 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)))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)))Complexity
Every variant above is time — each node’s nextnext pointer is touched a
constant number of times — and extra space, since nothing beyond
a handful of pointers (prevprev, currcurr, nxtnxt) is ever allocated. (kk-group
reversal’s recursion adds call-stack frames; an iterative version
gets back to true space if that matters.)
When to use it
Reach for this pattern whenever the interviewer says “reverse” and a linked
list is involved — whole list, a [left, right][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.
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
Problem. Reverse the nodes from position leftleft to position rightright
(1-indexed, inclusive) and return the head. Do it in one pass.
Constraints. 1 <= n <= 5001 <= n <= 500, 1 <= left <= right <= n1 <= left <= right <= n.
Examples. [1,2,3,4,5], left = 2, right = 4[1,2,3,4,5], left = 2, right = 4 gives [1,4,3,2,5][1,4,3,2,5] ·
[5], left = 1, right = 1[5], left = 1, right = 1 gives [5][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 currcurr 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 = 1left = 1means the segment starts at the real head, which would otherwise need its own branch.([1,2], 1, 2)([1,2], 1, 2)and([1,2,3], 1, 3)([1,2,3], 1, 3)cover that case. currcurrnever moves. It begins as the segment’s first node and ends as its last, which is correct after a reversal. Trying to advancecurrcurris the usual mistake.
The loop runs right - leftright - left times, not right - left + 1right - left + 1: reversing a kk-node
segment needs k - 1k - 1 relocations. ([5], 1, 1)([5], 1, 1) runs zero iterations and returns the
list unchanged.
Follow-ups: “Reverse in groups of kk (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 currcurr 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
Problem. Reverse the nodes of the list kk at a time. If the final group has
fewer than kk nodes, leave it as is. You may not alter the node values.
Constraints. 1 <= k <= n <= 50001 <= k <= n <= 5000.
Examples. [1,2,3,4,5], k = 2[1,2,3,4,5], k = 2 gives [2,1,4,3,5][2,1,4,3,5] ·
[1,2,3,4,5], k = 3[1,2,3,4,5], k = 3 gives [3,2,1,4,5][3,2,1,4,5] · [1,2], k = 3[1,2], k = 3 gives [1,2][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 >= kwhile remaining >= k condition simply stops, leaving the tail
untouched. The alternative — looking ahead kk nodes before each group — also works
and avoids the counting pass, but needs care to restore position when the lookahead
fails.
prev = currprev = curr after each group is the line worth understanding. Because head insertion
pushes currcurr to the back of its group, currcurr 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 = 1k = 1 returns the list unchanged (zero
relocations per group), a list shorter than kk 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 kk 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
Problem. Return TrueTrue if the linked list reads the same forwards and
backwards. Aim for time and space.
Constraints. 1 <= n <= 10^51 <= n <= 10^5, 0 <= Node.val <= 90 <= Node.val <= 9.
Examples. [1,2,2,1][1,2,2,1] gives TrueTrue · [1,2][1,2] gives FalseFalse ·
[1,2,1][1,2,1] gives TrueTrue
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 rightwhile right rather than while left and rightwhile left and right is the detail that handles
odd lengths. With [1,2,1][1,2,1], the reversed second half is [1,2][1,2] and the first half is
[1,2,1][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][::-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
| # | Problem | Difficulty | The twist |
|---|---|---|---|
| 206 | Reverse Linked List | Easy | The whole-list prev/curr/nextprev/curr/next template |
| 92 | Reverse Linked List II | Medium | Reverse only the [left, right][left, right] slice |
| 25 | Reverse Nodes in k-Group | Hard | Reverse fixed-size chunks, recursively |
| 24 | Swap Nodes in Pairs | Medium | K-group reversal with k = 2k = 2 |
Recap
- Every reversal variant is the same idea: walk once, flip
nextnextpointers 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
kknodes 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.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
