Algorithm

Remove Nth Node From End

Two Pointer Pattern

Remove Nth Node From End of List

Given the head of a linked list, remove the nth node from the end of the list and return its head.

CONSTRAINTS
  • The number of nodes in the list is sz.
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz
EXAMPLE 1
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Counting from the end, the 2nd node is 4. It is removed, leaving the other nodes in order.
EXAMPLE 2
Input: head = [1], n = 1
Output: []
The only node is also the 1st from the end, so removing it leaves an empty list.
EXAMPLE 3
Input: head = [1,2], n = 2
Output: [2]
The 2nd from the end is the head (node 1). Removing the head leaves [2].
Is n always valid?
Yes — the constraint 1 <= n <= sz guarantees n never exceeds the list length, so the target node always exists.
Could the node to remove be the head?
Yes, when n equals the list length. This is the case a dummy node in front of the head handles without any special branch.
One pass or two — does it matter?
Both are O(N) time. The single-pass two-pointer version is the expected answer because it removes the node in one walk and reads more cleanly than measure-then-walk.

We must delete the node that sits N places from the end of a singly linked list and return the head. The trouble is that "from the end" fights against how a linked list works: you can only move forward from the head, you can't look backward, and you don't know the length until you've walked all the way to the tail. Counting from the front is easy; the question asks us to count from the back.

The obvious way: measure the length, then walk to the target

Walk once to count the nodes — say there are 5. The N-th node from the end is the (5 − N)-th from the front, so its predecessor (the node we must rewire) is one before that. Walk a second time to that predecessor and splice the target out by pointing around it.

python
length = 0
curr = head
while curr:                       # first pass: measure
    length += 1
    curr = curr.next

curr = head
for _ in range(length - n - 1):   # second pass: walk to the predecessor
    curr = curr.next
curr.next = curr.next.next        # skip over the target

Correct, but it walks the list twice, and it's fiddly when the target is the head itself (there's no predecessor to rewire). Both problems disappear with the right two-pointer trick.

Hold two pointers exactly N apart

Here's the fixed-gap shape of the family. Instead of matching speeds, we lock in a distance. Send one pointer — the front runner — ahead by N steps first, while the other waits at the start. Now they are exactly N apart. From this moment, move both forward at the same speed, one step each. The gap never changes: it stays N the whole way. So the instant the front runner reaches the end of the list, the back pointer is sitting exactly N nodes from the end — right on top of the node we care about. We converted "N from the end" (which needs the length) into "keep a gap of N and walk to the end" (which doesn't). One pass, no counting.

One extra node makes the head case ordinary

To delete a node we actually need its predecessor, so we want the back pointer to stop one node before the target, not on it — that means starting the gap at N+1. But there's a snag: if the target is the very first node, its predecessor is nothing at all, and there's nowhere to stand. The clean fix is a dummy node: a throwaway node glued in front of the real head, pointing at it. Start both pointers on the dummy. Now every real node — including the original head — has a node in front of it to rewire, so deleting the head stops being a special case. At the end we return dummy.next, which is the head of whatever list remains.

python
dummy = Node(0, head)
lead = dummy
trail = dummy

for _ in range(n + 1):        # open a gap of N+1 between them
    lead = lead.next

while lead:                   # walk both until the front runner falls off
    lead = lead.next
    trail = trail.next

trail.next = trail.next.next  # trail is right before the target — splice it out
return dummy.next
Crucial Notethe gap is N+1, not N, and that single "+1" is what makes trail land on the predecessor rather than on the target itself — try it with N=1 on a short list and you'll see that a gap of only N leaves trail sitting on the doomed node with no way to unlink it. The dummy node earns its keep in exactly the case where the target is the head: trail stays on the dummy, dummy.next = dummy.next.next drops the old head, and dummy.next returns the new one — no branch needed.
Worked Example:head = [1, 2, 3, 4, 5], n = 2
The target is the 2nd from the end, node 4; its predecessor is node 3.
- Start both lead and trail on the dummy. Open the gap: move lead forward n+1 = 3 steps → dummy → 1 → 2 → 3. Now lead is on node 3.
- Walk both together until lead falls off: lead 3→4 (trail dummy→1), lead 4→5 (trail 1→2), lead 5→null (trail 2→3). Stop.
- trail is on node 3, exactly before the target. Splice: node 3's next skips node 4 to node 5. List becomes [1, 2, 3, 5].

This is the third face of same-direction two pointers you've now seen: not a speed ratio like the middle and cycle problems, but a constant offset. The reflex to build: any time a problem measures something "from the end" of a forward-only structure, ask whether a lead pointer set that many steps ahead can turn it into an ordinary walk to the end.

Interactive Strategy Visualization
GAP MEASUREMENT ENGINE

N-th Node Coordinate Mapping

1SlowFast2345NULL

Operation Status

The Silent End

We want to remove the N-th node from the end. But in a singly linked list, we can't see the finish line until we cross it. How do we target the unseen?

Tactical Insight

When the Fast pointer is N steps ahead, it "drags" the Slow pointer exactly N nodes behind it. Hits the end, found the target.

Strategy: The Dummy Node Trick

To handle edge cases (like removing the head), always start with a **Dummy** node pointing to the head.

O(N) Time Two Passes
O(N) Time · O(1) Space Fixed-Gap Single Pass