Given a linked list, reverse every consecutive group of k nodes, leaving any trailing remainder in its original order. The trick: walk ahead k steps to confirm a full group exists, then do a clean in-place reversal using a dummy head and a groupPrev pointer.
Given the head of a linked list and an integer k, reverse the nodes of the list k at a time and return the modified list. Nodes that are left over (fewer than k remaining) stay in their original order.
Concrete example: list = 1 → 2 → 3 → 4 → 5, k = 2.
Output: 2 → 1 → 4 → 3 → 5.
With k = 3 the same list gives 3 → 2 → 1 → 4 → 5 (one full group of three, two left over).
null, you 're done. Only then reverse the k nodes in-place. A dummy head node eliminates special-casing for the very first group, and a groupPrev pointer always sits just before the group being processed so you can re-stitch the chain.new ListNode(0, head). Set groupPrev = dummy. This lets the very first group be treated identically to later groups — no edge case for the head pointer.getKth(groupPrev, k) which walks k steps from groupPrev. If it returns null, fewer than k nodes remain → break. Otherwise the returned node is the kth node of the current group.groupNext = kth.next (the node immediately after the group). The group runs from groupPrev.next to kth.prev = groupNext, curr = groupPrev.next. Loop while curr !== groupNext, redirecting curr.next = prev and advancing both.kthis now the group's new head and the old head is the new tail. Set groupPrev.next = kth. Advance groupPrev to the old group head (now tail).groupPrev.next before reversal) becomes the tailof the reversed group. It's tempting to set groupPrev = groupPrev.next before saving the old head — that loses the reference. Always saveconst tmp = groupPrev.next first, then update.There are ⌊n/k⌋ full groups. Each group costs k steps for counting and k steps for reversal → 2k × (n/k) = 2ntotal steps. The remainder nodes are touched once. So it's truly O(n), not O(n·k).
5 nodes, k = 2. Scanning groups of 2 from the front.1// Definition for singly-linked list.2class ListNode {3 val: number;4 next: ListNode | null;5 constructor(val: number, next?: ListNode | null) {6 this.val = val;7 this.next = next ?? null;8 }9}1011function reverseKGroup(head: ListNode | null, k: number): ListNode | null {12▶ const dummy = new ListNode(0, head);13▶ let groupPrev = dummy; // tail of the last reversed group1415 // eslint-disable-next-line no-constant-condition16 while (true) {17 // 1. Count k nodes ahead of groupPrev — bail if fewer remain18 const kth = getKth(groupPrev, k);19 if (!kth) break;2021 const groupNext = kth.next; // node just after the group22 let prev: ListNode | null = groupNext;23 let curr: ListNode | null = groupPrev.next;2425 // 2. Reverse exactly k nodes26 while (curr !== groupNext) {27 const tmp = curr!.next;28 curr!.next = prev;29 prev = curr;30 curr = tmp;31 }3233 // 3. Re-stitch: groupPrev.next was the group head, now becomes the group tail34 const tmp = groupPrev.next!;35 groupPrev.next = kth; // kth became the new group head after reversal36 groupPrev = tmp; // advance groupPrev to the (old) group head (now tail)37 }3839 return dummy.next;40}4142/** Walk k steps from `node`; return that node, or null if fewer than k remain. */43function getKth(node: ListNode, k: number): ListNode | null {44 let curr: ListNode | null = node;45 for (let i = 0; i < k; i++) {46 curr = curr?.next ?? null;47 if (!curr) return null;48 }49 return curr;50}
// Definition for singly-linked list.
class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next?: ListNode | null) {
this.val = val;
this.next = next ?? null;
}
}
function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
const dummy = new ListNode(0, head);
let groupPrev = dummy; // tail of the last reversed group
// eslint-disable-next-line no-constant-condition
while (true) {
// 1. Count k nodes ahead of groupPrev — bail if fewer remain
const kth = getKth(groupPrev, k);
if (!kth) break;
const groupNext = kth.next; // node just after the group
let prev: ListNode | null = groupNext;
let curr: ListNode | null = groupPrev.next;
// 2. Reverse exactly k nodes
while (curr !== groupNext) {
const tmp = curr!.next;
curr!.next = prev;
prev = curr;
curr = tmp;
}
// 3. Re-stitch: groupPrev.next was the group head, now becomes the group tail
const tmp = groupPrev.next!;
groupPrev.next = kth; // kth became the new group head after reversal
groupPrev = tmp; // advance groupPrev to the (old) group head (now tail)
}
return dummy.next;
}
/** Walk k steps from `node`; return that node, or null if fewer than k remain. */
function getKth(node: ListNode, k: number): ListNode | null {
let curr: ListNode | null = node;
for (let i = 0; i < k; i++) {
curr = curr?.next ?? null;
if (!curr) return null;
}
return curr;
}dummy) with value 0 is prepended before head. groupPrevstarts here and will always point to the node just before the group we're about to reverse. This eliminates the need for a special case when the first group's head becomes the new list head.getKth(groupPrev, k) walks exactly k steps from groupPrev. If it hits null before finishing, fewer than k nodes remain and we break — those nodes stay untouched. Otherwise kth is the last node of the current group.prev = groupNext(the node after the group, which is the target for the future last-node's next) andcurr = groupPrev.next (the group's current head). The loop redirects each curr.next backward until we've processed all k nodes. After the loop prev points to the group's new head (the old kth).tmp (it is now the group's tail). Set groupPrev.next = kth to connect the preceding chain to the newly reversed group. Advance groupPrev = tmp so it sits at the tail of the reversed group — ready to be the anchor for the next group.k steps from node. Returns the node at position k or null if the list is too short. Using a helper keeps the main loop clean and easy to reason about.k nodes, recurse on the rest, and stitch the tail. But recursion uses O(n/k) stack space, so the iterative version is preferred.groupPrev.// Definition for singly-linked list.
class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next?: ListNode | null) {
this.val = val;
this.next = next ?? null;
}
}
function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
const dummy = new ListNode(0, head);
let groupPrev = dummy; // tail of the last reversed group
// eslint-disable-next-line no-constant-condition
while (true) {
// 1. Count k nodes ahead of groupPrev — bail if fewer remain
const kth = getKth(groupPrev, k);
if (!kth) break;
const groupNext = kth.next; // node just after the group
let prev: ListNode | null = groupNext;
let curr: ListNode | null = groupPrev.next;
// 2. Reverse exactly k nodes
while (curr !== groupNext) {
const tmp = curr!.next;
curr!.next = prev;
prev = curr;
curr = tmp;
}
// 3. Re-stitch: groupPrev.next was the group head, now becomes the group tail
const tmp = groupPrev.next!;
groupPrev.next = kth; // kth became the new group head after reversal
groupPrev = tmp; // advance groupPrev to the (old) group head (now tail)
}
return dummy.next;
}
/** Walk k steps from `node`; return that node, or null if fewer than k remain. */
function getKth(node: ListNode, k: number): ListNode | null {
let curr: ListNode | null = node;
for (let i = 0; i < k; i++) {
curr = curr?.next ?? null;
if (!curr) return null;
}
return curr;
}Reverse the first k nodes, then recurse on the rest and attach the recursed result as the new tail.
// Definition for singly-linked list.
class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next?: ListNode | null) {
this.val = val;
this.next = next ?? null;
}
}
function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
// 1. Walk k steps to confirm a full group exists.
let kth: ListNode | null = head;
for (let i = 0; i < k; i++) {
if (!kth) return head; // fewer than k remain — leave this tail as-is
kth = kth.next;
}
// Now kth points to the (k+1)-th node, i.e. the head of the *rest*.
// 2. Recurse on the rest first; its result becomes our group's new tail.
const newTail: ListNode | null = reverseKGroup(kth, k);
// 3. Reverse exactly k nodes, threading them onto newTail.
let prev: ListNode | null = newTail;
let curr: ListNode | null = head;
for (let i = 0; i < k; i++) {
const next: ListNode | null = curr!.next;
curr!.next = prev;
prev = curr;
curr = next;
}
// prev is now the new head of this reversed group.
return prev;
}head becomes the group's tail, we pre-seed prev with the already-processed remainder so the stitch is automatic. The catch is the call stack: one frame per group means O(n/k) extra space, which the iterative version avoids.Push k nodes onto a stack, then pop them to relink in reversed order.
// Definition for singly-linked list.
class ListNode {
val: number;
next: ListNode | null;
constructor(val: number, next?: ListNode | null) {
this.val = val;
this.next = next ?? null;
}
}
function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
const dummy = new ListNode(0, head);
let groupPrev: ListNode = dummy; // tail of the last emitted group
let curr: ListNode | null = head;
while (curr) {
const stack: ListNode[] = [];
// 1. Try to collect k nodes onto the stack.
let node: ListNode | null = curr;
let count = 0;
while (node && count < k) {
stack.push(node);
node = node.next;
count++;
}
// 2. Fewer than k remain? Leave them in their original order and stop.
if (count < k) {
groupPrev.next = curr;
break;
}
// 3. Pop the stack to relink the k nodes in reversed order.
while (stack.length > 0) {
groupPrev.next = stack.pop()!;
groupPrev = groupPrev.next;
}
// 4. node is the first node after this group — advance and detach the tail.
groupPrev.next = node;
curr = node;
}
return dummy.next;
}k nodes, we splice the original curr back onto groupPrevso the short remainder keeps its order. Space is O(k) for the stack, strictly worse than the O(1) pointer surgery, but it's an easy approach to derive under pressure.Copy every value into an array, reverse each complete block of kvalues in place (leaving a trailing remainder of fewer than kalone), then write the values back into the original nodes in order.
function reverseKGroup(head: ListNode | null, k: number): ListNode | null {
// 1. Collect node references and their values.
const nodes: ListNode[] = [];
for (let node = head; node !== null; node = node.next) nodes.push(node);
// 2. Reverse each full group of k values within the array.
for (let start = 0; start + k <= nodes.length; start += k) {
let i = start;
let j = start + k - 1;
while (i < j) {
const tmp = nodes[i].val;
nodes[i].val = nodes[j].val;
nodes[j].val = tmp;
i++;
j--;
}
}
// Trailing fewer-than-k nodes keep their original order automatically.
return head;
}valfields instead of relinking, the logic is dead simple, but the array of node references is O(n) extra space — and many interviewers consider value-swapping a cop-out when the task is really about pointer manipulation. The in-place reversal achieves the same result with O(1) space by re-wiring next pointers directly.| "reverse every k nodes" | dummy head + getKth + in-place reversal |
| surgically re-wire a subchain | save groupPrev before entering the group |
| "leave remainder untouched" | break when getKth returns null |
| reverse a subrange of a linked list | groupPrev pointer anchors the re-stitch |
const dummy = new ListNode(0, head);
let groupPrev = dummy;
while (true) {
const kth = getKth(groupPrev, k); // walk k steps
if (!kth) break; // fewer than k remain — leave them
const groupNext = kth.next;
let prev = groupNext, curr = groupPrev.next;
while (curr !== groupNext) { // reverse k nodes
const tmp = curr!.next;
curr!.next = prev;
prev = curr; curr = tmp;
}
const tmp = groupPrev.next!;
groupPrev.next = kth; // kth is new group head
groupPrev = tmp; // old head is new group tail
}
return dummy.next;groupPrev a valid starting position before the first node, so the first group is handled identically to all subsequent ones — no special case for updating head.reverseKGroup?1→2→3→4→5, k = 2. What is the output?prev initialized to groupNext (not null) at the start of each group reversal?