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 want to remove the N-th node from the very end of a singly linked list. But a linked list is a one-way chain of memory addresses; we cannot jump directly to a specific position, nor do we know its total length without walking it from start to finish. Finding a target from the front is simple, but how do we target a node relative to the back when we cannot look backward or see the end ahead of time?

The Double-Pass Tax

The most straightforward approach is to solve this in two separate trips. First, we walk the entire list to count how many nodes exist. Once we have the total length, we calculate the predecessor index of our target (Length - N). Then, we reset our pointer and take a second trip, walking exactly to that predecessor node and rewiring its pointer to skip the target.

While this works, it forces us to make two full passes over the list. Making two trips when we only need to delete a single node is a waste of execution time.

python
# Trip 1: Count total nodes
length = 0
curr = head
while curr:
    length += 1
    curr = curr.next

# Trip 2: Walk to the predecessor
target_idx = length - n
curr = head
for _ in range(target_idx - 1):
    curr = curr.next

# Rewire pointer to skip the target node
curr.next = curr.next.next
The Pointer State: The Scout & The Shadow

To delete the node in a single pass, we can use two pointers separated by a fixed distance. Each pointer has a strict role:
- The Scout (scout): Sprints ahead to establish a physical boundary gap.
- The Shadow (shadow): Follows at the exact same pace, positioned exactly N+1 steps behind the scout.

Both start parked at the very front of our list.

The Sentinel Safety Net

If the list has 5 nodes and we are asked to remove the 5th node from the end, we are actually removing the very first node (the head). In a naive implementation, removing the head requires unique code because it has no predecessor.

To unify our logic and avoid crashes, we attach a fake Dummy Node to the very front of the list, pointing to the original head. By starting both the scout and shadow at this dummy sentinel, every physical node—including the first one—is guaranteed to have a predecessor (shadow) to safely rewire its link.

The Fixed Gap

First, we tell the scout to advance exactly N+1 steps while the shadow remains stationary at the dummy node. This creates a perfect gap.

Once the gap is established, we advance both pointers one step at a time. Because the scout is exactly N+1 steps ahead, the exact moment the scout falls off the end of the list (becoming null), the shadow will land exactly one node before the target. We then perform the pointer surgery, bypassing the target node completely.

python
# Initialize dummy sentinel
dummy = Node(0, head)
scout = dummy
shadow = dummy

# 1. Establish the N + 1 node gap
for _ in range(n + 1):
    scout = scout.next

# 2. Advance both pointers in unison
while scout:
    scout = scout.next
    shadow = shadow.next

# 3. Surgery: Bypass the target node
shadow.next = shadow.next.next

# Return the new head (skipping dummy)
return dummy.next
Head Snipping and Lone Nodes

The sentinel-guided approach handles edge conditions with ease:
- Snipping the Head (N = Length): The scout moves all the way to null in the first step. The shadow stays at the dummy node. The bypass step rewires dummy.next = dummy.next.next, removing the original head seamlessly.
- Single-Node List (N = 1, length = 1): scout moves 2 steps to null. shadow stays at dummy. We bypass Node 1 and return null.

Worked Example:Snipping the Predecessor
0
shadowscout
1
2
3
4
5
NULL
We attach a dummy sentinel node containing 0 to the front and position both 'shadow' and 'scout' there.
0
shadow
1
2
3
scout
4
5
NULL
We establish a gap by moving 'scout' forward 3 steps (N+1) to node 3, while keeping 'shadow' at the dummy node.
0
1
2
3
shadow
4
5
NULL
We advance both pointers in unison. When 'scout' falls off the end of the list, 'shadow' is sitting at node 3, exactly one node before our deletion target (4).
0
1
2
3
4
5
NULL
We rewire node 3's link to skip node 4 and point directly to 5. We return dummy.next, leaving the updated list [1, 2, 3, 5].
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