Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Walk the list once with three pointers — prev, cur, next. For each node, save cur.next, flip cur.next to point at prev, then slide prev and cur forward. Return prev, which ends on the old tail (the new head). O(n) time, O(1) space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can rewire pointers in place without losing the list, manage the three-pointer bookkeeping, and recognise that the loop ends with prev (not cur) as the new head.
Where candidates slip
Overwriting cur.next = prev before saving the old next (orphaning the rest of the list), or returning cur/head instead of prev.
How to stand out
State O(n)/O(1), narrate the four-line loop body in order (save, flip, advance prev, advance cur), and mention this is the engine behind k-group reversal and palindrome checks.
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 previewprev = null, cur = headcur != null?NoYesExplore 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
Recursive Reversal
1Hint 1Explore hintHide hint
If the rest of the list (everything after head) were already reversed, how would you attach the current head onto its end?
2Hint 2Explore hintHide hint
Recurse to the end to get the new head. On the way back, make head.next point back at head, and set head.next = null.
3Hint 3Explore hintHide hint
Base case: an empty list or a single node is its own reverse — return head.
A · Core idea
Recurse all the way to the last node, which becomes the new head and is passed back up unchanged. As each call returns, it fixes the link between the current node and the one after it.
The base case. If head is null (empty) or head.next is null (single node), the list is already its own reverse — return head. The single-node case is what stops the recursion and supplies the new head that propagates back up.
The rewiring on the way back. Suppose head is node A and the recursive call has already reversed everything after it, returning newHead. The node directly after A is head.next; to flip that link we set head.next.next = head (the next node now points back at A), then head.next = null (A becomes the tail of whatever precedes it). We return newHead unchanged at every level so the original deepest node bubbles up as the final head.
Why O(n) space. Each node adds a frame to the call stack, so the stack is n deep at its peak. For n up to 10⁵ that risks a stack overflow — which is exactly why the iterative version is preferred in interviews.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
list = [1, 2, 3]Array state
deepest call
node 3 returned as newHead
[3, 2, 1]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(n) spaceOpen the invariant and proofExplore the proofHide the proof
The recursive version reads as 'reverse the tail, then hook the head onto the back of it'. It's beautiful but the O(n) stack is a real liability on long lists — always be ready to convert it to the O(1) iterative loop.
ComplexityO(n) time, O(n) space — One recursive call per node → O(n) time. The call stack reaches depth n before unwinding → O(n) space.
E · Edge cases and pitfalls5list = [] — Base case head == null → return null immediately.Explore edge casesHide edge cases
Edge cases handled
list = []Base case head == null → return null immediately.list = [7]Base case head.next == null → return the single node unchanged.
Pitfalls
- Returning head instead of newHead — head is the old (now last) node, not the new head.
- Forgetting head.next = null — leaves a cycle between the last two nodes.
- Risking stack overflow on very long lists because the recursion depth is O(n).
Approach 2 of 2 · Optimal
Iterative Three-Pointer Reversal (Optimal)
1Hint 1Explore hintHide hint
To flip a node's pointer to face backward, you need a handle on the node *behind* it. What pointer tracks that?
2Hint 2Explore hintHide hint
Keep three pointers: prev (node behind, starts null), cur (node you're flipping), nxt (saved successor so you don't lose the list).
3Hint 3Explore hintHide hint
Each iteration: save nxt = cur.next, set cur.next = prev, then move prev = cur and cur = nxt. When cur is null, prev is the new head.
A · Core idea
Maintain three pointers and flip one link per iteration. prev is the node we've already reversed up to (starts null), cur is the node whose link we're about to flip, and a temporary nxt holds cur.next so we don't lose the tail when we overwrite it.
Why save `nxt` first. The line cur.next = prev destroys cur's forward link. If we hadn't stashed it in nxt beforehand, the entire remainder of the list would be unreachable — orphaned the instant we overwrite the pointer. So the very first action each pass is nxt = cur.next.
The flip and the slide. After saving, cur.next = prev turns the link backward. Then we advance: prev = cur (the node we just finished becomes the new 'behind' marker) and cur = nxt (move on to the next node to flip). Both advances use values captured this pass, which is why ordering them as prev-then-cur is safe.
Why return `prev`. The loop ends when cur becomes null — i.e. we've walked off the end. At that moment prev points at the last node we flipped, which is the old tail and therefore the new head. Returning cur (null) or the original head (now the tail) are the two classic wrong answers.
Why `prev` starts null. The old head must become the new tail, and a tail's next is null. Initialising prev = null makes the very first flip set head.next = null automatically.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
list = [1, 2, 3]Array state
cur=1
1.next = null; prev=1, cur=2
[3, 2, 1]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The whole algorithm is four lines that never look more than one node ahead. 'Save next before you flip' is the single idea; everything else is bookkeeping. This exact loop, bounded to a range, is how you reverse a sublist or each k-sized group.
ComplexityO(n) time, O(1) space — Single pass over n nodes, constant work each → O(n) time. Three pointers regardless of list size → O(1) space.
E · Edge cases and pitfalls6list = [] — cur is null immediately; loop skipped; prev (null) returned → empty list.Explore edge casesHide edge cases
Edge cases handled
list = []cur is null immediately; loop skipped; prev (null) returned → empty list.list = [1, 2]1.next=null then 2.next=1; prev ends on 2 → returns [2, 1].
Pitfalls
- Writing cur.next = prev before saving nxt — orphans the rest of the list.
- Returning cur (null) or head (old tail) instead of prev.
- Initialising prev = head instead of null — creates a self-loop on the first node.
- Swapping the advance order so prev = cur runs after cur = nxt — then prev tracks the wrong node.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic ListNode reverseList(ListNode head) { ListNode prev = null; ListNode cur = head; while (cur != null) { ListNode nxt = cur.next;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Flipping before saving the next pointer
cur.next = prev overwrites the only reference to the rest of the list. Without saving nxt first, every node after cur is orphaned and the loop can't advance.
Explore the code comparisonHide the code comparison
Flipping before saving the next pointer
cur.next = prev overwrites the only reference to the rest of the list. Without saving nxt first, every node after cur is orphaned and the loop can't advance.
Don't write this
while (cur != null) {
cur.next = prev; // rest of list lost here
prev = cur;
cur = cur.next; // now reads prev, not the real successor
}Write this instead
while (cur != null) {
ListNode nxt = cur.next;
cur.next = prev;
prev = cur;
cur = nxt;
}2Returning the wrong pointer
When the loop ends cur is null and the original head is now the tail. The new head is prev, the last node whose link we flipped.
Explore the code comparisonHide the code comparison
Returning the wrong pointer
When the loop ends cur is null and the original head is now the tail. The new head is prev, the last node whose link we flipped.
Don't write this
while cur is not None:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return head # head is now the TAILWrite this instead
while cur is not None:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev # prev is the new headRevise
Carry the pattern into the interview
Pattern to remember
Three-pointer in-place reversal: save next, flip, advance
Code pattern to memorize
prev = None
cur = head
while cur:
nxt = cur.next # save before we overwrite
cur.next = prev # flip the link
prev = cur # advance prev
cur = nxt # advance cur
return prevRules to remember
- 1Save
cur.nextinto a temp before writingcur.next = prev, or you lose the rest of the list. - 2Return `prev`, not `cur`. When the loop ends
curis null;previs the old tail = new head. - 3Start
prev = nullso the old head correctly becomes the new tail (its next is null). - 4Advance in the right order: set
prev = curthencur = nxt— both use values captured this pass. - 5Reversal is in-place: you rewire existing nodes, you do not allocate new ones.
Use this pattern when
- Reverse a singly linked list in O(1) extra space
- Reverse a sub-segment (Reverse Linked List II) — same loop, bounded range
- Reverse in groups of k (Reverse Nodes in k-Group)
- Check a palindrome list by reversing the second half
Choose another pattern when
- You need the list unchanged → copy it, or use recursion that rebuilds, not in-place flip
- It's a doubly linked list → swap prev/next on each node instead of the three-pointer dance
- You only need to *print* in reverse → recurse and print on the way back, no rewiring needed
Interview takeaway
Three-pointer reversal is the most-asked linked-list manipulation. The whole trick is 'save next before you flip', and returning prev. Internalise this loop — k-group reversal, palindrome checks, and reorder-list all build on it.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternThree-pointer in-place reversal (save next, flip, advance prev and cur, return prev). It's the foundational pointer-rewiring pattern behind k-group reversal, palindrome checks, and reorder-list.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Reverse Linked List IIReverse only nodes between positions left and right — same loop, run for a bounded count.
- Reverse Nodes in k-GroupReverse each block of k nodes; the three-pointer loop is the per-group engine.
- Palindrome Linked ListFind the middle, reverse the second half, then compare the two halves.
- Reorder ListSplit at the middle, reverse the back half, then interleave the two halves.