Learn/DSA Patterns
DSA PatternsLinked Listsmedium8 min read

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.

#linked-list#two-pointers#n-gap#remove#beginner#interview
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 → 12345 → None

Step 1: advance fast 6 steps from dummy:
  dummy → 12345 → 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 → 2345 → 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:

ProblemGap setupWhat slow finds
Remove Nth from endfast starts N+1 aheadNode before the Nth-from-end
Find Nth from endfast starts N aheadExactly the Nth-from-end
Middle of list (Chapter 4.2)fast moves 2× speedThe middle (gap grows naturally)
Copy list with random pointerN/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 with fast = 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.next crash when target is the last node. If slow.next is the last node, slow.next.next is None — this is valid in Python (you're setting slow.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

ProblemDifficultyWhat to noticeLink
Remove Nth Node From End of ListMediumN+1 advance from dummy; dummy handles head removalLC #19
Find Nth From End (custom)EasyAdvance fast N steps (not N+1); slow lands on targetPractice variant
Delete the Middle NodeMediumFast/slow to find middle; then delete middleLC #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.