Remove Nth Node from End
Move the fast pointer N steps ahead, then walk both fast and slow together until fast reaches the end — slow is then exactly at the node just before the one to delete. This N-gap two-pointer trick removes the Nth-from-end node in a single O(n) pass with no length calculation.
Table of contents
Before we start
Removing the Nth node from the end requires knowing where the end is — but in a linked list, you can't look backward. The naive solution counts the length first, then removes. The elegant solution finds the target node in a single pass using two pointers with a fixed gap between them. By the end you will be able to:
- Explain the N-gap trick intuitively, not just mechanically.
- Write the solution with a dummy node to handle the edge case where the head itself is removed.
- Recognise the general "gap between two pointers" idea in other problems.
Picture this first (no code yet)
A real-life story
You are trying to find which carriage on a train is exactly N carriages from the end — without walking to the end first to count. Your strategy: ask a conductor at the front to walk N carriages ahead of you. Now you both walk at the same pace toward the back. When the conductor reaches the last carriage and steps off, you are standing exactly N carriages from the end.
The "conductor" is the fast pointer. "You" are the slow pointer. The N-carriage head start is the gap. Walking in sync keeps the gap constant until fast runs off the end.
First, the slow way (so you feel the pain)
Two-pass approach: traverse once to count length L, then traverse again to the (L − N)th node, then delete the next node.
def removeNthFromEnd_twopass(head, n):
length = 0
node = head
while node:
length += 1
node = node.next
# second pass: walk to position (length - n)
dummy = ListNode(0); dummy.next = head
node = dummy
for _ in range(length - n):
node = node.next
node.next = node.next.next
return dummy.next
Correct, but two passes over the list. For a list of 10 million nodes that is 20 million steps. The one-pass approach does it in 10 million steps — the same asymptotic class (O(n)) but half the constant.
The turning point
Pause & think
List: 1 → 2 → 3 → 4 → 5, n=2 (remove the 2nd from end, which is node 4).
If you advance fast exactly 2 steps before starting to move slow, and then move both together until fast reaches the last node — where is slow?
Try it: fast starts at node 1. Advance fast 2 steps → fast is at node 3. Now move both: fast→4, slow→2. fast→5, slow→3.
Fast is at the last node (node 5). Slow is at node 3 — the node before the one to delete (node 4).
The one idea to remember
The entire pattern in one sentence
Advance fast N steps ahead of slow, then walk both together until fast reaches the last node — slow is then at the node just before the target, so slow.next = slow.next.next deletes it in O(1).
The code, line by line
def removeNthFromEnd(head, n: int):
dummy = ListNode(0) # dummy handles the case where head itself is removed
dummy.next = head
slow = fast = dummy # both start at dummy (not head)
# Step 1: advance fast N+1 steps (N+1 because both start at dummy, not head)
for _ in range(n + 1):
fast = fast.next
# Step 2: walk both until fast is None (off the end)
while fast:
slow = slow.next
fast = fast.next
# Step 3: delete slow.next
slow.next = slow.next.next
return dummy.next
Why N+1 steps, not N? Both start at the dummy (position 0). We want slow to stop at the node before the target — not at the target itself. That requires one extra step of separation so that when fast is at None, slow is at the predecessor.
Alternative phrasing: advance fast N steps from the head (fast = head), then while fast.next: slow = slow.next; fast = fast.next — then slow lands at the node before the target. Both approaches work; use whichever you find clearer.
Watch it happen, frame by frame
List: 1 → 2 → 3 → 4 → 5, n=2
dummy → 1 → 2 → 3 → 4 → 5 → None
Step 1: advance fast N+1=3 steps from dummy:
fast: dummy → 1 → 2 → 3
slow=dummy, fast=node3
Step 2: walk both until fast=None:
Iter 1: slow=node1, fast=node4
Iter 2: slow=node2, fast=node5
Iter 3: slow=node3, fast=None → stop
Step 3: slow=node3. slow.next=node4 (the 2nd from end).
slow.next = slow.next.next = node5.
Result: dummy → 1 → 2 → 3 → 5 → None ✅
Edge case: n = 5 (remove the head, node 1)
dummy → 1 → 2 → 3 → 4 → 5 → None
Step 1: advance fast 6 steps from dummy:
dummy → 1 → 2 → 3 → 4 → 5 → None
After 6 steps: fast = None
Step 2: fast is already None → while loop doesn't execute. slow = dummy.
Step 3: slow.next = node1 (the head). slow.next = slow.next.next = node2.
Result: dummy → 2 → 3 → 4 → 5 → None ✅
Return dummy.next = node2 (new head).
Without the dummy node, removing the head would require special-casing if slow == dummy: return head.next. The dummy absorbs this edge case cleanly.
The general N-gap idea
This "fixed gap between two pointers" technique appears in several forms:
| Problem | Gap setup | What slow finds |
|---|---|---|
| Remove Nth from end | fast starts N+1 ahead | Node before the Nth-from-end |
| Find Nth from end | fast starts N ahead | Exactly the Nth-from-end |
| Middle of list (Chapter 4.2) | fast moves 2× speed | The middle (gap grows naturally) |
| Copy list with random pointer | N/A (hash map variant) | All nodes simultaneously |
Common traps
Watch out for these
- Using N steps instead of N+1 when starting from the dummy. If both start at dummy and you advance fast only N steps, slow stops at the target node itself (not the node before it). You need the predecessor to perform
slow.next = slow.next.next. Advance N+1 from dummy, or equivalently, advance N from head withfast = head. - Not using a dummy node. Without a dummy, removing the head (when n == length) requires a special case: checking if slow is still at the original position before the loop. The dummy eliminates this.
slow.next.nextcrash when target is the last node. Ifslow.nextis the last node,slow.next.nextis None — this is valid in Python (you're settingslow.next = None, which correctly removes the tail). Not a bug — but double-check your language's null behavior.
Remember this forever
Remove Nth from End — N-gap trick
dummy = ListNode(0); dummy.next = head
slow = fast = dummy
# advance fast N+1 steps (from dummy)
for _ in range(n + 1):
fast = fast.next
# walk both until fast = None
while fast:
slow = slow.next
fast = fast.next
# delete slow.next
slow.next = slow.next.next
return dummy.next
Trap: N+1 steps (not N) when starting both from the dummy.
Trap: always use a dummy to handle head-removal cleanly.
Check yourself
For a list of length 5 and n=2, trace exactly which node `fast` is on after the N+1 advance, and explain why slow ends up at node 3 (the predecessor of node 4).
Both start at dummy (position 0). After advancing fast N+1 = 3 steps:
- Step 1: fast → node1 (position 1)
- Step 2: fast → node2 (position 2)
- Step 3: fast → node3 (position 3)
Now slow=dummy (position 0), fast=node3 (position 3). Gap = 3 = N+1.
Walk together until fast=None:
- Iter 1: slow→node1, fast→node4. Gap still 3.
- Iter 2: slow→node2, fast→node5. Gap still 3.
- Iter 3: slow→node3, fast→None. Gap still 3.
Slow is at node3 (position 3). The Nth-from-end node is node4 (position 4 = length − n + 1 = 5 − 2 + 1 = 4). Node3 is exactly its predecessor. The N+1 head start guarantees slow stops one node before the target.
What happens if n equals the length of the list (removing the head)?
Fast advances n+1 = (length+1) steps from dummy. Since dummy + list has length+1 nodes (positions 0 through length), fast reaches position length+1 = None. The while loop doesn't execute. Slow stays at dummy. slow.next = slow.next.next becomes dummy.next = dummy.next.next = node2 (the second node). return dummy.next returns node2 as the new head — the original head is correctly removed. The dummy node makes this identical to all other cases, with no special handling needed.
Practice problems
| Problem | Difficulty | What to notice | Link |
|---|---|---|---|
| Remove Nth Node From End of List | Medium | N+1 advance from dummy; dummy handles head removal | LC #19 |
| Find Nth From End (custom) | Easy | Advance fast N steps (not N+1); slow lands on target | Practice variant |
| Delete the Middle Node | Medium | Fast/slow to find middle; then delete middle | LC #2095 |
This completes the first half of Chapter 4 — Linked Lists. Next up: Intersection of Two Linked Lists — the elegant "reset to the other list's head" trick that finds the intersection node in O(n + m) time, O(1) space.