Skip to content

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 leftleft through rightright), not the whole list.
  • How to extend that into reversing in groups of kk, recursively.
  • Why every variant stays O(n)O(n) time and O(1)O(1) 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.

reverse_whole_list.py
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)))
reverse_whole_list.py
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.

diagram Before: prev sits just outside the sublist, curr at its head mermaid
diagram After: nodes 2 through 4 are reversed in place, the rest untouched mermaid
reverse_between.py
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)))
reverse_between.py
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.

reverse_k_group.py
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)))
reverse_k_group.py
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 O(n)O(n) time — each node’s nextnext pointer is touched a constant number of times — and O(1)O(1) extra space, since nothing beyond a handful of pointers (prevprev, currcurr, nxtnxt) is ever allocated. (kk-group reversal’s recursion adds O(n/k)O(n/k) call-stack frames; an iterative version gets back to true O(1)O(1) 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 O(n)O(n), one pass. Space O(1)O(1).

Two things carry it:

  • The dummy head. left = 1left = 1 means 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.
  • currcurr never moves. It begins as the segment’s first node and ends as its last, which is correct after a reversal. Trying to advance currcurr is 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 O(n)O(n) — one counting pass plus one reversing pass. Space O(1)O(1).

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; O(n/k)O(n/k) 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 O(n)O(n) time and O(1)O(1) 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 O(n)O(n). Space O(1)O(1).

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 O(n)O(n)-space alternative is much easier and worth offering first: copy the values into a list and compare it with its reverse. The O(1)O(1) version is the improvement being asked for.

Follow-ups: “Restore the list?” — reverse the second half back before returning. ”O(n)O(n) space version?” — values into a list, compare with [::-1][::-1]. “Recursively?” — possible with a helper returning from the tail, but O(n)O(n) stack. “Why not compare from both ends directly?” — a singly linked list cannot be walked backwards.

LeetCode problem set

#ProblemDifficultyThe twist
206Reverse Linked ListEasyThe whole-list prev/curr/nextprev/curr/next template
92Reverse Linked List IIMediumReverse only the [left, right][left, right] slice
25Reverse Nodes in k-GroupHardReverse fixed-size chunks, recursively
24Swap Nodes in PairsMediumK-group reversal with k = 2k = 2

Recap

  • Every reversal variant is the same idea: walk once, flip nextnext pointers 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 kk nodes and recurses on the rest.
  • All variants: O(n)O(n) time, O(1)O(1) 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 coffee

Was this page helpful?

Let us know how we did