Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Use a dummy node before the head so the case 'left = 1' needs no special handling. Walk a pointer to the node just before position left, then reverse only the sublist from left to right. Two clean ways: (1) reverse the sublist in place with the prev/curr flip and reconnect the three boundary links, or (2) the one-pass head-insertion trick — repeatedly pull the node after curr to the front of the sublist. Both are O(n) time, O(1) space, single traversal.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can reverse a bounded segment in place without losing the rest of the list, handle 1-indexing and the inclusive range, and reconnect the boundary pointers correctly — especially the edge case left = 1.
Where candidates slip
Off-by-one on the walk to prevLeft, forgetting the dummy so left = 1 breaks, or mis-stitching the boundaries (leaving a gap or a cycle between the reversed segment and the tail).
How to stand out
Use a dummy node, show the one-pass head-insertion method, and articulate exactly which three links get rewired and why the original sub-head becomes the sub-tail that must point to the node after right.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewReverse sublist [left, right]left == 1? (head changes)Yes/NoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Reverse the Sublist In Place, Then Reconnect
1Hint 1Explore hintHide hint
Add a dummy node before head so reversing from position 1 isn't special. Walk a pointer to the node just before position left and remember it.
2Hint 2Explore hintHide hint
From there, run the ordinary prev/curr/next reversal, but only for the (right - left + 1) nodes in the window.
3Hint 3Explore hintHide hint
After reversing, you have three loose ends: the node before left, the new head of the reversed window (old position right), and the new tail (old position left) which must point to whatever followed right.
A · Core idea
This approach reuses the muscle memory from full-list reversal and confines it to a window.
The dummy node erases the head edge case. When left = 1, the reversal changes the list's head, which would otherwise require special handling. Prepending a dummy node whose next is head means the 'node before the window' always exists — even when the window starts at position 1, the node before it is simply the dummy. We return dummy.next at the end, which is the (possibly new) real head.
Find the anchor, then reverse the window. We walk beforeLeft forward left - 1 steps so it sits on the node immediately before position left. The window to reverse is then beforeLeft.next through the node at position right. We apply the classic three-pointer flip — prev, curr, next — but only right - left + 1 times, so it stops exactly at the window's end. When the loop finishes, prev points at the node that was at position right (the window's new head), and curr points at the node just past right (the start of the untouched suffix).
Reconnecting the three boundaries is the whole game. Reversing the window in isolation leaves it detached. Three links restore the list:
- 1The node that was first in the window (position
left) is now its tail. We captured it assubTail = beforeLeft.nextbefore rewiring, becausebeforeLeft.nextstill points there. - 2
beforeLeft.next = prevsplices the prefix onto the window's new head (oldright). - 3
subTail.next = currconnects the window's new tail to the suffix (the node after oldright).
Get any of those three wrong and you either drop the suffix, leave a gap, or create a cycle. Naming them explicitly — 'prefix → new head, new tail → suffix' — is what keeps the wiring straight under pressure.
Why capture subTail early. The original first node of the window becomes the tail after reversal, but its identity is easiest to grab before the reversal mutates pointers: it's exactly beforeLeft.next at the moment we arrive. Stash it, then reverse, then use it to attach the suffix.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
head = [1,2,3,4,5], left = 2, right = 4Array state
anchor
beforeLeft=node1, window=[2,3,4]
[1,4,3,2,5]C · Code with teaching comments
Reference implementation previewpublic class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; }Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
A bounded reversal is just the ordinary reversal with a step counter, sandwiched between two pointer hand-offs. The dummy node neutralizes the head case, and the only genuinely new idea versus full reversal is that the first node of the window becomes the node you must reattach to the suffix.
ComplexityO(n) time, O(1) space — One walk to position left (≤ n steps) plus one reversal pass over the window (≤ n steps) — a single overall traversal, so O(n) time. Only a constant number of pointers are used, so O(1) space.
E · Edge cases and pitfalls7left == 1 (reverse from head) — beforeLeft stays on the dummy; dummy.next is rewired to the new head, returned correctly.Explore edge casesHide edge cases
Edge cases handled
left == 1 (reverse from head)beforeLeft stays on the dummy; dummy.next is rewired to the new head, returned correctly.left == rightGuarded up front — the function returns the list unchanged.right == n (window reaches the tail)curr ends as null; subTail.next = null correctly terminates the list.
Pitfalls
- Forgetting the dummy node — left = 1 then needs a separate code path.
- Off-by-one on the walk: stopping ON position left instead of just before it.
- Reversing right - left times instead of right - left + 1 (drops the last window node).
- Capturing subTail after the reversal, when beforeLeft.next no longer points to it.
Approach 2 of 2 · Optimal
Head-Insertion — True One Pass
1Hint 1Explore hintHide hint
Keep curr fixed on the window's first node. Each step, take the node right after curr and splice it to the front of the window (just after prevLeft).
2Hint 2Explore hintHide hint
Doing that (right - left) times slides every later window node ahead of curr, which naturally leaves curr at the back — the window is reversed.
3Hint 3Explore hintHide hint
Three pointer assignments per step: detach curr.next, point it at prevLeft.next, then make it the new prevLeft.next.
A · Core idea
Head-insertion reverses the window without ever building it in isolation — it threads each node to the front as it goes, so the list stays connected the whole time.
The fixed anchor and the fixed curr. prevLeft sits just before the window (again, the dummy guarantees it exists). curr is pinned to the window's first node and never moves forward — instead, the nodes behind it get pulled ahead of it. Because curr ends up at the back of the window, it naturally becomes the reversed segment's tail.
One splice per step. On each iteration we grab nxt = curr.next, the node immediately after curr. Three assignments relocate it to the front of the window:
- 1
curr.next = nxt.next— unhooknxt, socurrnow skips over it. - 2
nxt.next = prevLeft.next— pointnxtat the current front of the window. - 3
prevLeft.next = nxt— makenxtthe new front.
After this, nxt has jumped to the head of the window and everything else shifted back by one. Repeating right - left times pulls every later window node in front of curr, fully reversing the segment.
Why it's genuinely one pass. There's no 'reverse then reconnect' split: the boundary links are maintained continuously by the three assignments. prevLeft.next always points at the current window head, and curr.next always points at the suffix, so the list is valid after every step. That continuous correctness is what makes head-insertion the canonical answer to the 'can you do it in one pass?' follow-up.
The order of the three lines is fixed. Detach before you redirect, redirect before you re-anchor. Swap any two and you either lose nxt.next or strand part of the window — the same discipline as the classic three-pointer flip, just applied to relocation instead of in-place reversal.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
head = [1,2,3,4,5], left = 2, right = 4Interactive diagram
Picking the right approach
startA dummy node plus a bounded reversal; head-insertion is the elegant one-pass variant.
[1,4,3,2,5]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Head-insertion reframes 'reverse a window' as 'repeatedly move the second window node to the front'. Keeping curr stationary and sliding others past it means the list never breaks apart, so there's nothing to reconnect at the end — the elegance interviewers reward on the one-pass follow-up.
ComplexityO(n) time, O(1) space — A single walk to prevLeft plus right - left splice operations, each O(1). One overall traversal → O(n) time, O(1) space with a constant number of pointers.
E · Edge cases and pitfalls7left == 1 — prevLeft is the dummy; the new head emerges as dummy.next, returned correctly.Explore edge casesHide edge cases
Edge cases handled
left == 1prevLeft is the dummy; the new head emerges as dummy.next, returned correctly.left == rightLoop runs zero times; list unchanged.window of two nodesOne splice swaps them; list stays valid throughout.
Pitfalls
- Reordering the three splice lines — detach, then redirect, then re-anchor.
- Looping right - left + 1 times instead of right - left (over-rotates the window).
- Advancing curr each iteration — curr must stay fixed as the window's first node.
- Skipping the dummy and special-casing left = 1.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic ListNode reverseBetween(ListNode head, int left, int right) { if (head == null || left == right) { return head; } ListNode dummy = new ListNode(0, head);Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Skipping the dummy node
When left = 1 the head itself changes. Without a dummy you must branch on 'is left 1?' and reattach the head manually — error-prone. A dummy makes 'the node before the window' always exist.
Explore the code comparisonHide the code comparison
Skipping the dummy node
When left = 1 the head itself changes. Without a dummy you must branch on 'is left 1?' and reattach the head manually — error-prone. A dummy makes 'the node before the window' always exist.
Don't write this
// no dummy: must special-case left == 1
ListNode beforeLeft = head;
for (int i = 0; i < left - 2; i++) beforeLeft = beforeLeft.next;
// ...and separate logic when left == 1 to update headWrite this instead
ListNode dummy = new ListNode(0, head);
ListNode beforeLeft = dummy;
for (int i = 0; i < left - 1; i++) beforeLeft = beforeLeft.next;
// left == 1 needs no special case; return dummy.next2Forgetting to capture the sublist tail
In the reverse-then-reconnect method, the window's first node becomes its tail and must point to the suffix. If you don't capture it before reversing, beforeLeft.next no longer points to it and the suffix is lost.
Explore the code comparisonHide the code comparison
Forgetting to capture the sublist tail
In the reverse-then-reconnect method, the window's first node becomes its tail and must point to the suffix. If you don't capture it before reversing, beforeLeft.next no longer points to it and the suffix is lost.
Don't write this
prev = None
curr = before_left.next
for _ in range(right - left + 1):
nxt = curr.next; curr.next = prev; prev = curr; curr = nxt
before_left.next = prev
# forgot to connect the old sub-head to the suffix -> tail danglesWrite this instead
sub_tail = before_left.next # capture BEFORE reversing
prev = None
curr = before_left.next
for _ in range(right - left + 1):
nxt = curr.next; curr.next = prev; prev = curr; curr = nxt
before_left.next = prev
sub_tail.next = curr # reattach the suffix3Wrong iteration count on the window
The inclusive range [left, right] has right - left + 1 nodes. The reverse-then-reconnect flip runs that many times; the head-insertion method runs right - left times (one fewer, since curr stays put). Mixing them up over- or under-reverses.
Explore the code comparisonHide the code comparison
Wrong iteration count on the window
The inclusive range [left, right] has right - left + 1 nodes. The reverse-then-reconnect flip runs that many times; the head-insertion method runs right - left times (one fewer, since curr stays put). Mixing them up over- or under-reverses.
Don't write this
# head-insertion but looping one too many times
for _ in range(right - left + 1):
nxt = curr.next
curr.next = nxt.next; nxt.next = prev_left.next; prev_left.next = nxtWrite this instead
# head-insertion: exactly right - left splices
for _ in range(right - left):
nxt = curr.next
curr.next = nxt.next; nxt.next = prev_left.next; prev_left.next = nxtRevise
Carry the pattern into the interview
Pattern to remember
Dummy node + bounded in-place sublist reversal
Code pattern to memorize
dummy -> head; prevLeft = dummy
advance prevLeft (left-1) times
# head-insertion, repeat (right-left) times:
curr = prevLeft.next
for _ in range(right - left):
nxt = curr.next
curr.next = nxt.next
nxt.next = prevLeft.next
prevLeft.next = nxt
return dummy.nextRules to remember
- 1Use a dummy node in front of
headso reversing from position 1 (left = 1) isn't a special case. - 2Stop the leading walk at the node just before
left(prevLeft) — you need it to re-link the reversed head. - 3Positions are 1-indexed and the range
[left, right]is inclusive. - 4Re-link three boundaries: prevLeft → new sub-head, old sub-head (now sub-tail) → the node after
right. - 5It's a single pass in O(1) space — no second traversal and no extra list.
Use this pattern when
- Reverse only a contiguous segment of a singly linked list
- Any in-place pointer rewrite bounded by two positions
- Problems that reverse a window then stitch it back (k-group, partial reversals)
- When a dummy node simplifies head-adjacent edge cases
Choose another pattern when
- You must reverse the WHOLE list → plain Reverse Linked List (no boundaries)
- Reverse in fixed-size blocks repeatedly → Reverse Nodes in k-Group
- You may not mutate the input → build a new list instead of rewiring pointers
Interview takeaway
Reverse Linked List II is the partial-reversal workhorse: a dummy node erases the head edge case, and the only real subtlety is reconnecting the three boundary links. Master the head-insertion one-pass here and Reverse Nodes in k-Group becomes a short step away.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternDummy node + bounded in-place reversal. The dummy removes the head edge case; the head-insertion variant reverses a window in one pass without a reconnect step. Same family as Reverse Linked List, Reverse Nodes in k-Group, and Swap Nodes in Pairs — recognise it whenever you reverse only a segment and must stitch it back.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Reverse Linked ListThe unbounded version — reverse the whole list with the prev/curr/next flip and no boundaries.
- Reverse Nodes in k-GroupReverse every consecutive block of k nodes — repeated bounded reversal stitched together.
- Swap Nodes in PairsComing soonA k-group with k = 2 — reverse pairs using a dummy node and boundary relinking.
- Reorder ListFind the middle, reverse the second half, then interleave — partial reversal is the core subroutine.
Same pattern