Remove Nth Node From End of List

MediumLinked ListTwo Pointers

The Prompt

Given the `head` of a linked list, remove the `n`-th node from the end of the list and return its head.

Understanding the Problem

"Nth from the end" is awkward for a singly linked list because you only discover where the end is after you have walked past everything. The two-pass fix โ€” count the length, then walk again to node length โˆ’ n โ€” works but the interviewer wants one pass.

The one-pass trick is a fixed gap: send a fast pointer n + 1 steps ahead of slow (both starting at a dummy node before the head), then move the two pointers in lockstep. When fast steps off the end, slow is standing exactly one node before the victim โ€” perfectly placed to unlink it.

The Interview Flow

Interviewer

How can you remove the Nth node from the end of a list in a single pass?

Candidate

A single pass suggests I can't first find the length and then iterate again. The two-pointer technique seems appropriate here.

Interviewer

Describe how you'd use two pointers.

Candidate

I'll use a `fast` pointer and a `slow` pointer. I will first advance the `fast` pointer `n` steps ahead. This creates a gap of `n` nodes between `fast` and `slow`.

Interviewer

What do you do after creating the gap?

Candidate

Then, I move both `fast` and `slow` pointers one step at a time until the `fast` pointer reaches the end of the list. At this point, the `slow` pointer will be positioned just before the node that needs to be removed.

Interviewer

So you can just update `slow.next`?

Candidate

Exactly. I can set `slow.next = slow.next.next` to bypass and effectively remove the desired node. There is an edge case to handle, though: if the node to be removed is the head itself. I can solve this by using a dummy node that points to the head, and starting my `slow` pointer there.

Interviewer

Using a dummy node is a great way to handle that edge case. Please implement this approach.

Why does a fixed gap land slow just before the target?

The invariant: fast stays exactly n + 1 nodes ahead of slow for the entire lockstep walk, because both move one step per tick. When fast becomes null (one past the last node), slow must be n + 1 nodes before null โ€” that is, at the node immediately before the nth-from-the-end. One assignment, slow.next = slow.next.next, removes it.

Starting slow at a dummy node (not the head) is what makes the edge case free: if the head itself must be removed, slow ends up on the dummy and the same assignment works, then dummy.next is returned. One traversal, two pointers: O(L) time, O(1) space โ€” the same asymptotics as two passes but half the walking, and the single-pass constraint satisfied.

One-Pass Solution with Two Pointers

  • Create a `dummy` node and set `dummy.next = head`. This simplifies handling the edge case where the head needs to be removed.
  • Initialize two pointers, `fast` and `slow`, both starting at the `dummy` node.
  • Advance the `fast` pointer `n+1` steps. This is `n+1` because we started `slow` at the dummy node, so we need the gap to be `n` between `slow` and the node to be removed.
  • Now, move both `fast` and `slow` pointers forward one step at a time, until `fast` becomes `null`.
  • When `fast` is `null`, `slow` will be pointing to the node right before the one we need to remove.
  • Perform the removal by updating the pointer: `slow.next = slow.next.next`.
  • Return `dummy.next`, which is the head of the modified list.

Try it yourself

Write your solution and run it against 3 test cases.

Loading...

JavaScript, TypeScript & Python run sandboxed in your browser; other languages run on the execution server. Your code is saved locally as you type.

Final Solution

function removeNthFromEnd(head, n) {
  const dummy = new ListNode(0, head);
  let left = dummy;
  let right = head;
  
  for (let i = 0; i < n; i++) {
    right = right.next;
  }
  
  while (right) {
    left = left.next;
    right = right.next;
  }
  
  // delete
  left.next = left.next.next;
  
  return dummy.next;
}

Explanation

Remove the 2nd node from the end of 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5 (that is node 4) in a single pass.

1Both pointers start at the dummy. Advance fast n + 1 = 3 steps to node 3 โ€” the gap between fast and slow is now fixed.

2Move both in lockstep 3 more steps: fast walks 4, 5, null; slow walks 1, 2, 3. Fast fell off, so slow (at 3) sits just before the victim, node 4.

3Unlink with slow.next = slow.next.next: 3 now points at 5, bypassing 4. Return dummy.next โ€” the list is 1 โ†’ 2 โ†’ 3 โ†’ 5.

Complexity Analysis

TIME

O(n)

SPACE

O(1)

Finished working through this one?

Mark it complete to track it on your Data Structures path.