ListNode reverseList(ListNode head) {
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
} func reverseList(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
return prev
} def reverse_list(head):
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev fn reverse_list(head: Option<Box<ListNode>>)
-> Option<Box<ListNode>> {
let (mut prev, mut curr) = (None, head);
while let Some(mut node) = curr {
curr = node.next.take();
node.next = prev;
prev = Some(node);
}
prev
} function reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
} Reverse linked lists or portions of them without extra space using pointer manipulation.
The In-Place Reversal pattern reverses a linked list (or a portion of it) by redirecting pointers rather than creating new nodes. Instead of building a new list, you simply flip the direction of each arrow.
The technique uses three pointers — prev, curr, and next — to walk through the list and reverse each link one at a time.
Original list: arrows point forward. Reversed list: every arrow points backward.
Core principle: You never allocate new nodes. You simply change where each node's .next pointer points. This gives you O(1) space and O(n) time — the optimal solution for any linked list reversal problem.
Reach for the in-place reversal pattern whenever you see these signals:
Why not use a stack? You could push all nodes onto a stack and rebuild, but that uses O(n) extra space. In-place reversal achieves the same result with O(1) space by manipulating pointers directly. Interviewers specifically look for this optimization.
The reversal follows a simple four-step dance at each node. Think of it as "save, flip, advance, advance":
next = curr.next — we are about to overwrite curr.next, so save it first.curr.next = prev — point current node backward instead of forward.prev = curr — prev moves forward to where curr is now.curr = next — curr moves to the saved next node.// The Core Reversal Template ListNode prev = null, curr = head; while (curr != null) { ListNode next = curr.next; // 1. save next curr.next = prev; // 2. flip the arrow prev = curr; // 3. advance prev curr = next; // 4. advance curr } return prev; // new head
// The Core Reversal Template var prev *ListNode = nil curr := head for curr != nil { next := curr.Next // 1. save next curr.Next = prev // 2. flip the arrow prev = curr // 3. advance prev curr = next // 4. advance curr } return prev // new head
# The Core Reversal Template prev, curr = None, head while curr is not None: nxt = curr.next # 1. save next curr.next = prev # 2. flip the arrow prev = curr # 3. advance prev curr = nxt # 4. advance curr return prev # new head
// The Core Reversal Template let mut prev: Option<Box<ListNode>> = None; let mut curr = head; while let Some(mut node) = curr { curr = node.next.take(); // 1. save next, detach node.next = prev; // 2. flip the arrow prev = Some(node); // 3. advance prev // curr already advanced in step 1 } prev // new head
// The Core Reversal Template let prev: ListNode | null = null, curr = head; while (curr !== null) { const next = curr.next; // 1. save next curr.next = prev; // 2. flip the arrow prev = curr; // 3. advance prev curr = next; // 4. advance curr } return prev; // new head
Why save next first? The moment you execute curr.next = prev, you lose access to the rest of the original list. If you have not saved curr.next beforehand, you cannot move forward. This is the single most common mistake in linked list reversal.
The simplest variant: reverse every node from head to tail. This is the direct application of the core template with no modifications.
Given the head of a singly linked list, reverse the list, and return the reversed list.
class Solution { public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; // save curr.next = prev; // flip prev = curr; // advance prev curr = next; // advance curr } return prev; // prev is the new head } }
func reverseList(head *ListNode) *ListNode { var prev *ListNode curr := head for curr != nil { next := curr.Next // save curr.Next = prev // flip prev = curr // advance prev curr = next // advance curr } return prev // prev is the new head }
def reverse_list(head: ListNode | None) -> ListNode | None: prev: ListNode | None = None curr = head while curr is not None: nxt = curr.next # save curr.next = prev # flip prev = curr # advance prev curr = nxt # advance curr return prev # prev is the new head
impl Solution { pub fn reverse_list(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> { let mut prev: Option<Box<ListNode>> = None; let mut curr = head; while let Some(mut node) = curr { curr = node.next.take(); // save & detach node.next = prev; // flip prev = Some(node); // advance prev } prev // prev is the new head } }
function reverseList(head: ListNode | null): ListNode | null { let prev: ListNode | null = null; let curr = head; while (curr !== null) { const next = curr.next; // save curr.next = prev; // flip prev = curr; // advance prev curr = next; // advance curr } return prev; // prev is the new head }
Recursive alternative: You can also reverse recursively — recurse to the end, then set head.next.next = head on the way back. But the iterative version is preferred in interviews because it uses O(1) space instead of O(n) call stack space.
Reverse only the nodes from position m to position n, keeping the rest of the list intact. This requires three phases: navigate to position m, reverse the sublist, and reconnect the ends.
1 → 2 → 3 → 4 → 5 becomes 1 → 4 → 3 → 2 → 5
class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if (left == right) return head; // Dummy node simplifies edge cases when left == 1 ListNode dummy = new ListNode(0); dummy.next = head; // Phase 1: Navigate to position left-1 ListNode before = dummy; for (int i = 1; i < left; i++) before = before.next; // Phase 2: Reverse from left to right ListNode prev = null; ListNode curr = before.next; for (int i = 0; i < right - left + 1; i++) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; } // Phase 3: Reconnect before.next.next = curr; // tail of reversed connects to right+1 before.next = prev; // before connects to new head of reversed return dummy.next; } }
func reverseBetween(head *ListNode, left int, right int) *ListNode { if left == right { return head } // Dummy node simplifies edge cases when left == 1 dummy := &ListNode{Next: head} // Phase 1: Navigate to position left-1 before := dummy for i := 1; i < left; i++ { before = before.Next } // Phase 2: Reverse from left to right var prev *ListNode curr := before.Next for i := 0; i < right-left+1; i++ { next := curr.Next curr.Next = prev prev = curr curr = next } // Phase 3: Reconnect before.Next.Next = curr // tail of reversed connects to right+1 before.Next = prev // before connects to new head of reversed return dummy.Next }
def reverse_between(head: ListNode | None, left: int, right: int) -> ListNode | None: if left == right: return head # Dummy node simplifies edge cases when left == 1 dummy = ListNode(0, head) # Phase 1: Navigate to position left-1 before = dummy for _ in range(1, left): before = before.next # Phase 2: Reverse from left to right prev: ListNode | None = None curr = before.next for _ in range(right - left + 1): nxt = curr.next curr.next = prev prev = curr curr = nxt # Phase 3: Reconnect before.next.next = curr # tail of reversed connects to right+1 before.next = prev # before connects to new head of reversed return dummy.next
impl Solution { pub fn reverse_between( head: Option<Box<ListNode>>, left: i32, right: i32 ) -> Option<Box<ListNode>> { if left == right { return head; } // Collect nodes into a vec, splice, rebuild let mut nodes: Vec<Box<ListNode>> = Vec::new(); let mut curr = head; while let Some(mut node) = curr { curr = node.next.take(); nodes.push(node); } // Reverse the subslice [left-1 .. right] let (l, r) = (left as usize - 1, right as usize - 1); nodes[l..=r].reverse(); // Rebuild linked list from back to front let mut head: Option<Box<ListNode>> = None; for mut node in nodes.into_iter().rev() { node.next = head; head = Some(node); } head } }
function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null { if (left === right) return head; // Dummy node simplifies edge cases when left == 1 const dummy = new ListNode(0, head); // Phase 1: Navigate to position left-1 let before: ListNode = dummy; for (let i = 1; i < left; i++) before = before.next!; // Phase 2: Reverse from left to right let prev: ListNode | null = null; let curr = before.next; for (let i = 0; i < right - left + 1; i++) { const next = curr!.next; curr!.next = prev; prev = curr; curr = next; } // Phase 3: Reconnect before.next!.next = curr; // tail of reversed connects to right+1 before.next = prev; // before connects to new head of reversed return dummy.next; }
The dummy node trick: When the reversal starts at position 1, there is no "before" node. A dummy node placed before the head eliminates this edge case entirely. After reversal, dummy.next always points to the correct new head.
Process k nodes at a time, reversing each group independently, then connecting all groups together. If fewer than k nodes remain at the end, leave them as-is.
1 → 2 → 3 → 4 → 5 → 6 → 7 with k=3
class Solution { public ListNode reverseKGroup(ListNode head, int k) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode groupPrev = dummy; while (true) { // Check if k nodes remain ListNode check = groupPrev; for (int i = 0; i < k; i++) { check = check.next; if (check == null) return dummy.next; } // Reverse k nodes ListNode prev = null; ListNode curr = groupPrev.next; for (int i = 0; i < k; i++) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; } // Connect reversed group ListNode groupTail = groupPrev.next; // was first, now last groupTail.next = curr; // connect to next group groupPrev.next = prev; // connect from previous // Move to next group groupPrev = groupTail; } } }
func reverseKGroup(head *ListNode, k int) *ListNode { dummy := &ListNode{Next: head} groupPrev := dummy for { // Check if k nodes remain check := groupPrev for i := 0; i < k; i++ { check = check.Next if check == nil { return dummy.Next } } // Reverse k nodes var prev *ListNode curr := groupPrev.Next for i := 0; i < k; i++ { next := curr.Next curr.Next = prev prev = curr curr = next } // Connect reversed group groupTail := groupPrev.Next // was first, now last groupTail.Next = curr // connect to next group groupPrev.Next = prev // connect from previous // Move to next group groupPrev = groupTail } }
def reverse_k_group(head: ListNode | None, k: int) -> ListNode | None: dummy = ListNode(0, head) group_prev = dummy while True: # Check if k nodes remain check = group_prev for _ in range(k): check = check.next if check is None: return dummy.next # Reverse k nodes prev: ListNode | None = None curr = group_prev.next for _ in range(k): nxt = curr.next curr.next = prev prev = curr curr = nxt # Connect reversed group group_tail = group_prev.next # was first, now last group_tail.next = curr # connect to next group group_prev.next = prev # connect from previous # Move to next group group_prev = group_tail
impl Solution { pub fn reverse_k_group( head: Option<Box<ListNode>>, k: i32 ) -> Option<Box<ListNode>> { // Collect all nodes into a vec for index-based access let mut nodes: Vec<Box<ListNode>> = Vec::new(); let mut curr = head; while let Some(mut node) = curr { curr = node.next.take(); nodes.push(node); } let n = nodes.len(); let k = k as usize; let mut i = 0; // Reverse each full k-group in place while i + k <= n { nodes[i..i + k].reverse(); i += k; } // Remaining nodes (fewer than k) are left as-is // Rebuild linked list from back to front let mut result: Option<Box<ListNode>> = None; for mut node in nodes.into_iter().rev() { node.next = result; result = Some(node); } result } }
function reverseKGroup(head: ListNode | null, k: number): ListNode | null { const dummy = new ListNode(0, head); let groupPrev: ListNode = dummy; while (true) { // Check if k nodes remain let check: ListNode | null = groupPrev; for (let i = 0; i < k; i++) { check = check!.next; if (check === null) return dummy.next; } // Reverse k nodes let prev: ListNode | null = null; let curr = groupPrev.next; for (let i = 0; i < k; i++) { const next = curr!.next; curr!.next = prev; prev = curr; curr = next; } // Connect reversed group const groupTail = groupPrev.next!; // was first, now last groupTail.next = curr; // connect to next group groupPrev.next = prev; // connect from previous // Move to next group groupPrev = groupTail; } }
The connection pattern: After reversing a group, groupPrev.next (which was the group's first node) becomes the group's last node. So groupPrev.next gets reassigned to prev (new first), and the old first node's next points to curr (start of next group). Master this reconnection and the rest is just the basic reversal template.
These LeetCode problems are solved cleanly using the in-place reversal pattern.
#234 Palindrome Linked List is a great combination problem: use fast & slow pointers to find the middle, reverse the second half in-place, then compare both halves node by node. It combines two patterns in O(n) time and O(1) space.
Watch the three pointers dance through the list, flipping arrows one at a time.
Time O(n): Each node is visited exactly once. We do constant work per node (save, flip, advance, advance). For partial reversals, we still visit at most n nodes total.
Space O(1): We only use three pointer variables (prev, curr, next) regardless of list size. No arrays, stacks, or recursion needed.
K-Group variant: Still O(n) time. Each node is reversed exactly once. The "check if k nodes remain" step adds another pass, but it is at most n total checks across all groups.
Compare with recursive reversal: The recursive version has O(n) call stack space, making it O(n) space. The iterative in-place approach is strictly better in space complexity. This is why interviewers prefer the iterative solution.
Before flipping curr.next, save it. Losing access to the rest of the list is the most common reversal bug.
Save, flip, advance prev, advance curr. Four operations per node, same order every time. Memorize this rhythm.
For partial reversals, the old first node becomes the new last. Connect it to the node after the reversed section, and connect the node before to the new first (prev).
When reversal might change the head, add a dummy node before the list. It eliminates "what if left == 1" edge cases cleanly.
Flip the Arrows, Not the Nodes
Linked list reversal is not about moving data — it is about redirecting pointers. Three variables, four operations per node, constant space. Once you internalize the three-pointer dance, every reversal variant becomes a straightforward extension of the same core idea.