Skip to content

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.

  • The reusable prev / curr / next three-pointer template.
  • How to reverse only a sublist (positions left through right), not the whole list.
  • How to extend that into reversing in groups of k, recursively.
  • Why every variant stays O(n)O(n) time and O(1)O(1) extra space.

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.

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)))

Four assignments per node, and their order is the entire problem. Step through one iteration at a time and watch what nxt is protecting:

listOnly the arrows move — the nodes never go anywhereLC 206 · O(n) time, O(1) space
12345prevcur
prevNonecur1
setupThree pointers, and the order of the four assignments inside the loop is everything. prev starts at null because the old head becomes the new tail — its next must end up pointing at nothing.
1/17

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.

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.

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 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.

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)))

Whole-list reversal — 1234. Each row is one iteration of the four-line loop:

currprevnxt (saved first)list built so far
1None21
21321
324321
43None4321

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.next must come first. The very next line overwrites curr.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 off nxt. At every step the list is temporarily two lists — that invariant is what makes the loop easy to verify by hand.
  • prev starts at None, and that is what terminates the reversed list. The original head becomes the tail, and its next must be None; seeding prev = head instead 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.

iterationnode pulled forwardwhole list afterwards
1313245
2414325

Answer 14325, in right - left = 2 iterations.

  • curr stays 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 let prev adopt it. Swap the last two and moved.next points at itself.
  • A dummy node removes the left == 1 special case, exactly as on the dummy-head page — otherwise reversing from position 1 has to update head separately.

Every variant above is O(n)O(n) time — each node’s next pointer is touched a constant number of times — and O(1)O(1) extra space, since nothing beyond a handful of pointers (prev, curr, nxt) is ever allocated. (k-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.)

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.

ProblemWhat changesThe catch
LC 206 Reverse Linked Listthe base loopreturn prev, not curr
LC 92 Reverse Linked List IIreverse only [left, right]use a dummy so left == 1 needs no special case; curr never moves
LC 25 Reverse Nodes in k-Groupreverse each full group, leave a short tail alonecount k nodes before reversing — LC 24 is this with k = 2
LC 24 Swap Nodes in Pairsk = 2often written directly with prev/first/second naming
LC 234 Palindrome Linked Listfind the middle, reverse the second half, comparepoliteness: restore the list afterwards if the caller keeps it
LC 143 Reorder Listreverse the second half, then interleavesee Copy Flatten and Reorder
LC 445 Add Two Numbers IIreverse both, add with carry, reverse the resultor use two stacks and skip the mutation entirely
LC 2074 Reverse Even Length Groupsgroup sizes grow 1, 2, 3, …reverse a group only when its actual length is even, which the tail may not be
Doubly linked listswap each node’s prev and nextone loop, no third pointer needed
Reverse by value rather than by pointercopy values into a list, write them back reversedO(n)O(n) space; legitimate when node identity must be preserved
They askWhat they’re checkingThe answer
“Why save nxt before flipping?”Whether you understand the hazardBecause 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 slipprev. When the loop exits, curr is None and prev is the last node visited, i.e. the new head
“Do it recursively”Breadthreverse(head.next) then wire head.next.next = head; head.next = None. Same O(n)O(n) time but O(n)O(n) stack, so on a 10510^5-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 rightWhether you can adapt itWalk 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 carefullyCount 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 O(1)O(1) space”CompositionFast/slow to the middle, reverse the second half, compare in step, and restore if the caller still needs the list. Anything else costs O(n)O(n) space
“Is your solution destructive?”Engineering judgementYes — the caller’s list is rewired. Say it unprompted, and offer the value-copy version (O(n)O(n) space) when node identity or the original order matters
“Doubly linked list instead”GeneralitySwap each node’s prev and next in one pass; no third pointer is needed because the backward link already holds what nxt was saving

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 O(n)O(n), one pass. Space O(1)O(1).

Two things carry it:

  • The dummy head. left = 1 means 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.
  • curr never moves. It begins as the segment’s first node and ends as its last, which is correct after a reversal. Trying to advance curr is 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 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 >= 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; O(n/k)O(n/k) stack. “Why is this Hard?” — the group-boundary bookkeeping, not the reversal itself.

Problem. Return True 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^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 O(n)O(n). Space O(1)O(1).

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 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]. “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.

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.

4 problems
1 easy2 medium1 hard

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.

pch.quizTag In-place reversal — self-check
  1. Why must `nxt = curr.next` come before `curr.next = prev`?

    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.

  2. What does the loop return, and why is that a common mistake?

    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.

  3. In sublist reversal (LC 92), why does `curr` never move?

    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.

  4. The recursive reversal is elegant. Why prefer the iterative version?

    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.

  5. LC 25 reverses in groups of k, leaving a short final group untouched. What is the standard bug?

    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.

  6. Your reversal passes the tests. What should you volunteer about it?

    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.

  • Cue — “reverse” a list, a [left, right] slice, or groups of k; or an O(1)O(1)-space follow-up on any list problem.
  • The loop, four linesnxt = curr.nextcurr.next = prevprev = currcurr = nxt. Save before flipping.
  • Return prev, never curr (which is None) and never head (now the tail).
  • prev = None at the start is what terminates the reversed list; seeding it with head makes a cycle.
  • Sublist (LC 92) — dummy, walk prev to left - 1, then pull the node after curr to the front right - left times. curr never moves.
  • Groups of k (LC 25)count k ahead before reversing; leave a short tail alone.
  • CostO(n)O(n) time, O(1)O(1) space. Recursive is O(n)O(n) 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 next 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 k 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.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading