Remove Nth Node from End — NullPointer on Head Removal
NullPointerException when removing head (n==list length) crashes browser tab's history pruner.
20+ years shipping performance-critical code where algorithms decide the bill. Everything here is grounded in real deployments.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- Core idea: advance fast pointer n+1 steps ahead of slow. Move both until fast reaches null. Slow is now before the target node.
- Deletion: set slow.next = slow.next.next to skip the target node.
- Dummy node: eliminates the head-removal edge case — slow always has a predecessor.
- Time: O(L) one pass. Space: O(1) — two pointer variables only.
- Why n+1 not n: slow must land on the node BEFORE the target, not on the target itself.
- Production risk: forgetting the dummy node means head removal requires a separate code path.
Imagine two runners on a track. You tell the fast runner to start n steps ahead. They both run at the same speed. When the fast runner crosses the finish line, the slow runner is exactly n positions from the end. You then ask the slow runner to skip the next person in line — that skipped person is the one you wanted to remove. A dummy person placed before the start line handles the case where the person to skip is the very first runner.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
Removing the nth node from the end of a linked list is a positional deletion problem — you need to find a node by its distance from the tail without random access. The naive approach counts the length first (two passes), then walks to position L-n. The optimal approach uses a two-pointer gap in a single pass.
The critical edge case is removing the head itself (when n equals list length). Without a dummy sentinel node, this requires a separate if-branch. The dummy node makes the deletion logic uniform — slow always has a predecessor, so slow.next = slow.next.next works for every position including the head.
This pattern generalizes: any problem requiring 'find the kth element from the end' uses the same two-pointer gap technique. It is the complement of Floyd's fast/slow pattern used for cycle detection and middle-finding.
Why Removing the Nth Node from the End Is a Two-Pointer Problem
Removing the nth node from the end of a singly linked list means deleting the node that is n steps before the tail, where n is 1-indexed (n=1 removes the last node). The core mechanic uses two pointers — fast and slow — with the fast pointer advanced n steps ahead. Then both move together until fast reaches the end; slow now points to the node just before the target. This achieves the removal in one pass, O(n) time and O(1) space.
The critical property: you must handle the case where n equals the list length — that is, removing the head. In that scenario, the fast pointer becomes null after advancing n steps, and the standard loop condition fails. You detect this by checking if fast is null after the initial advance; if so, the head is the target. Without this check, you get a NullPointerException when trying to access fast.next in the loop. The two-pointer technique works because the gap between fast and slow is exactly n, so when fast lands on the last node, slow is at the node before the removal point.
Use this pattern whenever you need to find or modify a node at a known offset from the end without knowing the list length. It's common in streaming data, real-time log processing, and memory-constrained environments where you cannot buffer the entire list. The technique is also the foundation for more complex problems like rotating a list or finding the middle node — mastering it prevents off-by-one errors that crash production pipelines.
How Remove Nth Node From End Works — Step by Step
The classic approach uses two pointers separated by exactly n nodes so that when the fast pointer reaches the end, the slow pointer is at the (n+1)th node from the end — the node just before the target.
- Create a dummy node before the head to handle edge cases like removing the head itself.
- Set fast = dummy and slow = dummy.
- Advance fast n+1 steps forward (n steps to create the gap, +1 so slow lands before the target).
- Move both fast and slow one step at a time until fast reaches None.
- Now slow.next is the node to delete. Set slow.next = slow.next.next.
- Return dummy.next as the new head.
For list 1→2→3→4→5, remove 2nd from end (target: node 4): 1. Advance fast 3 steps: fast=node3. 2. Move together: slow=node1,fast=node4; slow=node2,fast=node5; slow=node3,fast=None. 3. Delete: node3.next = node3.next.next = node5. List: 1→2→3→5.
- Imagine a window of fixed width n+1 sliding across the list.
- The left edge of the window is slow. The right edge is fast.
- When the right edge hits the end, the left edge is exactly n+1 positions from the end.
- The node immediately after the left edge (slow.next) is the nth from the end — the deletion target.
Worked Example — Tracing the Two-Pointer Approach
List: 1→2→3→4→5 (length 5). Remove n=2 (2nd from end = node with value 4).
Initial state: dummy→1→2→3→4→5→None. fast=slow=dummy.
- Step 1: fast=pos1 (node 1)
- Step 2: fast=pos2 (node 2)
- Step 3: fast=pos3 (node 3)
- Iteration 1: slow=pos1 (node 1), fast=pos4 (node 4). fast is not None.
- Iteration 2: slow=pos2 (node 2), fast=pos5 (node 5). fast is not None.
- Iteration 3: slow=pos3 (node 3), fast=None. fast is None. Stop.
slow is at node 3. slow.next is node 4 (the target). Delete: slow.next = slow.next.next (node 5).
Result: dummy→1→2→3→5→None. Return dummy.next → 1→2→3→5.