Reverse Linked List
Problem statement
Statement
Given the head of a singly linked list, reverse the list in place and return the new head.
Examples
head = 1 → 2 → 3 → 4 → 5 → null
→ 5 → 4 → 3 → 2 → 1 → null
head = 1 → 2 → null
→ 2 → 1 → null
head = null
→ null
head = 1 → null
→ 1 → null
Constraints
0 ≤ list length ≤ 5000-5000 ≤ Node.val ≤ 5000- Solve in O(1) extra memory if asked.
What this problem is really testing
The simplest possible exam of pointer discipline: do you understand that assigning curr.next = prev destroys the only reference to the original successor unless you save it first? Every linked-list problem you'll see later (cycle detection, merging, reordering) relies on this exact pattern. It's the four-line drill that earns its place by being the substrate of everything that follows.
Hints
Hints
- Visualise the arrows. Draw the list. Each arrow represents
node.next. Reversing the list means flipping every arrow. - You need three local variables. Call them
prev,curr, andnext.prevtracks where the reversed portion ends;currtracks the next node to flip;nextis a temporary so you don't lose the original successor. - Save before you overwrite. The line
nxt = curr.nextmust come beforecurr.next = prev. Otherwise the originalcurr.nextreference is lost forever. - Each iteration: save, flip, advance, advance. Save
nxt, flipcurr.next, advanceprevtocurr, advancecurrtonxt. - Return
prev, nothead. When the loop ends,curris null andprevis the new head (the old tail). The originalheadreference still points at what is now the last node. - Recursive option. Recurse on
head.next, then sethead.next.next = headandhead.next = None. Elegant but uses O(n) call stack, so the iterative version is preferred in production.
Solution and approaches
Solution: two approaches
Approach 1 · Iterative: O(n) time, O(1) space
The canonical answer. Three local variables, one loop, no allocation.
def reverse_list(head):
prev = None
curr = head
while curr is not None:
nxt = curr.next # save the original successor
curr.next = prev # flip the arrow
prev = curr # advance prev
curr = nxt # advance curr
return prev # the old tail is the new head
Each iteration moves three local variables and rewires one arrow: strict constant work. The loop runs once per node, so the total is O(n). The space cost is the three local variables; no auxiliary data structure.
Why save nxt first: the moment you assign curr.next = prev, the original curr.next pointer is overwritten. If you didn't save it into nxt beforehand, the rest of the list is lost; there is no way to advance curr forward.
Why return prev: at the end of the loop, curr is null (we walked off the end of the original list). prev is the last node we processed, which was the original tail. After all the flips, the original tail's next points to its original predecessor, which points to its predecessor, and so on. The original tail is now the head of the reversed list.
Approach 2 · Recursive: O(n) time, O(n) call stack
Elegant but uses linear stack space. Worth knowing for the structural insight; not the version to ship to production on long lists.
def reverse_list_rec(head):
if head is None or head.next is None:
return head # base case: empty or single node
new_head = reverse_list_rec(head.next)
head.next.next = head # the next node now points back at us
head.next = None # we become the new tail
return new_head
The recursive call returns the new head, but along the way each frame performs the rewiring for its own pair of nodes. The base case returns the original tail (which is the new head). Then each unwound frame sets head.next.next = head, making the recursive call's "current head" point backward to head. The final head.next = None ensures that the eventual new tail terminates correctly.
Complexity comparison
| Approach | Time | Space | Notes |
|---|---|---|---|
| Iterative | O(n) | O(1) | Production answer |
| Recursive | O(n) | O(n) stack | Stack overflow on lists > ~10⁴ nodes in Python |
Edge cases to verify
- Empty list:
head = None→None. The loop body never executes;prevstays atNone. - Single node:
head = 1 → null→1 → null. The loop runs once:nxt = null,1.next = null(was already),prev = 1,curr = null. Returnsprev = 1. - Two nodes:
1 → 2 → null→2 → 1 → null. The smallest non-trivial test; if this works, longer lists work. - Self-loop input. The problem statement assumes no cycles. If a cycle is present, the iterative version loops forever, production code that must defend against this would need a separate cycle check via Floyd's algorithm.
Common mistakes
Where beginners go wrong
- Forgetting
nxt = curr.next. The most common bug. You assigncurr.next = prev, then try to docurr = curr.next, butcurr.nextis nowprev, so you walk backward instead of forward. The save line is not stylistic; it preserves the only reference to the rest of the list. - Returning
headinstead ofprev. After the loop,headstill points at what is now the last node. The new head isprev. This bug is silent: the function returns a non-null pointer, but to the wrong end of the list. - Initialising
prev = headinstead ofprev = None. The original head must end up as the new tail withnext = None. Initialisingprev = headcreates a self-loop on the first iteration. - Trying to swap data instead of pointers. Works for primitive payloads but breaks for any non-trivial node type, breaks for lists with shared nodes, and is two passes (find length, swap from both ends) instead of one. The interview answer is always pointer reversal.
- Recursive without a base case. Forgetting
if head is None or head.next is None: return headcauses infinite recursion. The base case must handle both the empty list and the single-node list.
Interview follow-ups to expect
- "Reverse the list between positions m and n." See In-Place Reversal Patterns: the same loop, with a dummy head and a count-to-position step before the reversal.
- "Reverse in groups of K." Same loop applied to each K-node window, with stitching between windows. Skip the final group if it has fewer than K nodes.
- "Reverse a doubly linked list." Two pointers per node makes this slightly different: you swap each node's
prevandnextin place. One traversal; head becomes tail. - "Check if a list is a palindrome." Reverse the second half (use Floyd's to find the midpoint), then compare with the first half. O(n) time, O(1) memory.
- "Reverse without modifying nodes; return a new reversed list." Allocate fresh nodes during traversal; the original list stays intact. O(n) time and space.
Related reading
- In-Place Reversal Patterns, the deep-dive that exercises this primitive on K-groups and arbitrary ranges.
- Floyd's Tortoise & Hare, the other foundational pointer-discipline drill.
- Linked Lists, the module that frames why this problem matters.