Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Walk the list once, flipping each node's next pointer to point backwards. Keep three references — prev, curr, next — and at every step save curr.next, repoint curr.next to prev, then slide prev and curr forward by one. Return prev (the new head). O(n) time, O(1) space iteratively; the recursive version is the same idea but O(n) stack space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you can manipulate pointers in place without losing the list — specifically whether you save the next node before overwriting the current node's link.
Where candidates slip
Overwriting curr.next before saving it (losing the rest of the list), or returning the original head / curr instead of prev.
How to stand out
Offer both iterative O(1)-space and recursive solutions, explain why prev starts as null, and connect the flip to Reorder List and Reverse k-Group.
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 a singly linked listIs O(1) extra space required?YesNo / follow-upExplore 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 · Optimal
Iterative — Three-Pointer Flip (Optimal)
1Hint 1Explore hintHide hint
To reverse a singly linked list you have to flip each next pointer. But the moment you do curr.next = prev, you've lost the link to the rest of the list. How do you avoid that?
2Hint 2Explore hintHide hint
Keep three references: prev (the node behind you, initially null), curr (the node you're flipping), and a temp for curr.next saved before you overwrite it.
3Hint 3Explore hintHide hint
Loop while curr != null: save nxt = curr.next, set curr.next = prev, then advance prev = curr and curr = nxt. When curr is null, prev is the new head.
A · Core idea
We rebuild the list's links one node at a time, dragging a prev pointer behind us. At each node we redirect its next to point at the node we just came from.
Why we need three pointers. A singly linked list only lets you go forward. The instant you execute curr.next = prev, the original forward link is gone — so before that line you must stash it in a temporary nxt. The three roles are: prev is everything already reversed, curr is the node being flipped right now, and nxt is the untouched remainder. Lose any one of them and you either corrupt the list or strand half of it.
Why prev starts as null. The original head becomes the new tail, and a tail's next must be null. By initialising prev = null, the very first flip sets head.next = null for free, correctly terminating the reversed list.
Why the four lines are order-sensitive. Save → flip → advance prev → advance curr. If you advance curr before flipping, you flip the wrong node. If you flip before saving nxt, you lose the rest of the list. The sequence is a tiny state machine and every step depends on the previous one.
Where we end up. The loop continues until curr walks off the end and becomes null. At that exact moment prev is sitting on the last real node — which is the new head — so we return prev. Returning curr (null) or the original head is the classic off-by-one bug.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
head = [1, 2, 3]Array state
start
prev=null, curr=1, nxt=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 trick is a sliding window of two adjacent nodes (prev, curr) plus a one-node lookahead (nxt). Once you internalise 'save the link you're about to destroy', every in-place linked-list rewrite — partial reversal, k-group reversal, reorder — becomes the same muscle memory.
ComplexityO(n) time, O(1) space — Exactly one pass: each node is visited once and does O(1) pointer work. No auxiliary data structure, so space is O(1).
E · Edge cases and pitfalls5head = [] — curr = null on entry, the while loop never runs, prev stays null and we return null — the empty list.Explore edge casesHide edge cases
Edge cases handled
head = []curr = null on entry, the while loop never runs, prev stays null and we return null — the empty list.head = [1]One iteration: nxt=null, 1.next=null (already), prev=1, curr=null. Returns the single node unchanged.
Pitfalls
- Writing curr.next = prev before saving nxt — you lose the rest of the list.
- Returning curr (null) or the original head instead of prev.
- Forgetting to initialise prev to null, leaving the new tail pointing at garbage / a cycle.
Approach 2 of 2
Recursive — Flip on the Way Back Up
1Hint 1Explore hintHide hint
Imagine the list is already reversed from the second node onward. What single pair of pointer assignments fixes up the first node?
2Hint 2Explore hintHide hint
Recurse to the end first. The base case (null or single node) returns itself as the new head and bubbles that head back up unchanged.
3Hint 3Explore hintHide hint
On the way back, head.next is the node right after head. Set head.next.next = head to make it point back, then head.next = null so head becomes the new tail.
A · Core idea
We let recursion carry us to the tail, then fix one link per frame as the stack unwinds.
The recursive leap of faith. Assume reverseList(head.next) already fully reverses everything after head and hands back the new head of that reversed sublist. That new head never changes again — we just thread it straight back up through every return. Our only job at this frame is to attach head onto the end of that reversed sublist.
The two magic lines. After the recursive call, head.next still points at the node that is now the tail of the reversed portion. So head.next.next = head makes that tail point back to head, splicing head on as the new last node. Then head.next = null severs head's old forward link so it terminates the list correctly. Skip the second line and you create a two-node cycle between head and its neighbour.
Base case. When head is null (empty list) or head.next is null (single node / the original tail), there's nothing to flip, so we return head as the new head. This is the value that propagates unchanged all the way back to the top-level caller.
Why it costs O(n) space. There's no loop; the work lives in n stacked frames. For the 5000-node constraint here that's fine, but on a million-node list it would overflow the stack — which is exactly why the iterative version is the safer default to lead with.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
head = [1, 2, 3]call(1)
recurse on 2
stack: 1 → 2 → 3[3, 2, 1]C · Code with teaching comments
Reference implementation previewpublic ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode newHead = reverseList(head.next);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
Recursion reverses the list 'from the back': the deepest frame establishes the new head, and each shallower frame welds one more node onto the growing tail. It's elegant but trades the iterative version's O(1) space for O(n) stack — a trade worth naming out loud.
ComplexityO(n) time, O(n) space — Each of the n nodes triggers one recursive call doing O(1) work → O(n) time. The recursion depth equals the list length, so the call stack is O(n) space.
E · Edge cases and pitfalls3Forgetting head.next = null — leaves a cycle between the last two nodes.Explore edge casesHide edge cases
Pitfalls
- Forgetting head.next = null — leaves a cycle between the last two nodes.
- Returning head instead of the bubbled-up newHead.
- Ignoring stack depth — O(n) frames can overflow on very long lists.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; }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
Once curr.next = prev runs, the original forward link is gone. You must stash it first or you can never reach the rest of the list.
Explore the code comparisonHide the code comparison
Flipping before saving the next pointer
Once curr.next = prev runs, the original forward link is gone. You must stash it first or you can never reach the rest of the list.
Don't write this
while (curr != null) {
curr.next = prev; // rest of list is now unreachable
ListNode nxt = curr.next; // this is prev, not the real next!
prev = curr;
curr = nxt;
}Write this instead
while (curr != null) {
ListNode nxt = curr.next; // save FIRST
curr.next = prev;
prev = curr;
curr = nxt;
}2Returning the wrong node
When the loop exits, curr is null and head points to null (it's the new tail). Only prev holds the new head.
Explore the code comparisonHide the code comparison
Returning the wrong node
When the loop exits, curr is null and head points to null (it's the new tail). Only prev holds the new head.
Don't write this
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return head # head is now the tail!Write this instead
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return prev3Recursive cycle from a missing null assignment
After head.next.next = head, the two nodes point at each other. Without head.next = null you've built an infinite 2-node loop.
Explore the code comparisonHide the code comparison
Recursive cycle from a missing null assignment
After head.next.next = head, the two nodes point at each other. Without head.next = null you've built an infinite 2-node loop.
Don't write this
new_head = self.reverseList(head.next)
head.next.next = head
# missing head.next = None -> cycle
return new_headWrite this instead
new_head = self.reverseList(head.next)
head.next.next = head
head.next = None
return new_headRevise
Carry the pattern into the interview
Pattern to remember
Three-pointer in-place pointer reversal
Code pattern to memorize
prev = None
curr = head
while curr:
nxt = curr.next # save before you overwrite
curr.next = prev # flip the link
prev = curr # advance prev
curr = nxt # advance curr
return prevRules to remember
- 1Save
curr.nextinto a temp before you overwrite it — otherwise you lose the rest of the list. - 2Return prev, not curr. When the loop ends
curris null andprevis the new head. - 3Initialise
prev = null, so the old head'snextbecomes null and correctly terminates the reversed list. - 4The order inside the loop is fixed: save next → flip → advance prev → advance curr. Reordering breaks it.
- 5Recursive version: recurse to the tail, then
head.next.next = head; head.next = nullon the way back up.
Use this pattern when
- You must physically reverse a singly linked list or a sub-segment of one
- A larger problem needs a reversed second half (Reorder List, Palindrome Linked List)
- Reversing in groups of k (Reverse Nodes in k-Group) — same flip, bounded window
- Any time you need to traverse a singly linked list backwards without extra memory
Choose another pattern when
- You only need to *read* the list backwards once → push to a stack or use recursion's call stack
- It's a doubly linked list → swapping prev/next pointers per node is the natural move
- You must not mutate the input → build a new reversed list instead of in-place flipping
Interview takeaway
The three-pointer flip is the single most reused linked-list primitive in interviews. Master `prev/curr/next` here and you get Reorder List, Palindrome Linked List, and Reverse Nodes in k-Group almost for free.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical in-place pointer reversal. The prev/curr/next flip is the atomic operation behind Reverse Linked List II, Reverse Nodes in k-Group, Palindrome Linked List, and Reorder List. Recognise it whenever you must change link direction without extra memory.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Reverse Linked List IIReverse only the sublist between positions left and right — use a dummy node and reverse a bounded window.
- Reverse Nodes in k-GroupReverse every consecutive block of k nodes; same flip but stop after k and stitch groups together.
- Palindrome Linked ListFind the middle with slow/fast, reverse the second half, then compare both halves.
- Reorder ListReverse the second half and interleave — reversal is the core subroutine.