Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Fast & Slow Pointers, which you haven’t finished yet.

P2MediumBuilt on Fast & Slow Pointers~7 min speedrun

Fixed-Offset Pointers (Nth Node From End)

Built on
Fast & Slow PointersStart one pointer n steps ahead, then move both one step at a time, so the gap between them stays fixed.
The problem

Remove Nth Node From End of List (LC 19)(opens leetcode.com)

Your input is a linked list's head plus an integer n. Delete the node that sits n positions from the end of the list, then return the (possibly new) head.

Follow up: Can you find and delete that node in one pass, without first counting the list's length?

Example 1
Input:head = [1,2,3,4,5], n = 2
Output:[1,2,3,5]
The 2nd node from the end is 4. Removing it yields 1 -> 2 -> 3 -> 5.
12345

Fixed-Offset Pointers

list + n ➔ lead n steps ahead of trail ➔ move both together, trail stops n steps behind lead (Fixed Gap)
Spot it when: A singly linked list stores no length, and you must reach or remove the node that is n nodes from the end, in one pass with O(1) extra space.
Fixed-Offset Pointers
Python
1from typing import Optional
2 
3class ListNode:
4 def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
5 self.val = val
6 self.next = next
7 
8def remove_nth_from_end(head: Optional[ListNode], n: int) -> Optional[ListNode]:
9 """Remove Nth Node From End of List: keep lead n steps ahead of trail, so trail stops just before the target.
10 L = number of nodes in the list.
11 Time: O(L) | Space: O(1)
12 """
13 dummy = ListNode(0, head) # Every real node, the head too, now has a node before it
14 lead = trail = dummy # Both start on dummy, so lead is 0 steps ahead of trail
15 
16 # 1. Open The Gap:
17 # Moving lead n nodes forward while trail stays put leaves lead exactly n steps ahead of trail.
18 for _ in range(n):
19 lead = lead.next
20 
21 # 2. Move Both Pointers Together:
22 # Each pass moves lead and trail one node, so lead stays n steps ahead. lead stops on the last node, which puts trail on the node just before the n-th node from the end.
23 while lead.next:
24 lead = lead.next
25 trail = trail.next
26 
27 # 3. Unlink The Target:
28 # trail.next is the n-th node from the end. Skipping it removes it, and when it is the head, trail is dummy, so the head needs no special case.
29 trail.next = trail.next.next
30 
31 # 4. Return Through The Dummy, Not head:
32 # head itself may have been removed, so dummy.next, not head, is always the first node of the result.
33 return dummy.next
Invariant: After the first n steps, lead is exactly n steps ahead of trail, and every pass of the loop moves both by one node, so the gap is still n.

A singly linked list has no link back, so you cannot walk from its end. Move lead forward n nodes while trail stays on dummy. Now lead is n steps ahead of trail. Then move both one node per loop pass. When lead is the last node, trail is n steps behind it, on the node just before the one to remove.

The first loop sets the gap to n. Each pass of the second loop moves lead and trail by one node, so the gap stays n. For a list of L nodes, lead ends on node L and trail ends on node L - n. The node after trail is the n-th node from the end. The code never computes L. When n equals L, trail stays on dummy, so the head is removed like any other node.

The trap is the pair of the gap size and the loop test. The loop while lead.next stops lead on the last node, so the gap must be n, set with range(n). If you write while lead instead, lead runs one node further, to None, and the gap must be n + 1. Mixing the two leaves trail one node away from the right place, and the code removes the wrong node.

See it run

Remove Nth Node From End of List: remove_nth_from_end([1, 2, 3, 4, 5, 6, 7, 8], n = 3)

lead dummy · trail dummy · gap 0

lead 1 · trail dummy · gap 1

lead 2 · trail dummy · gap 2

lead 3 · trail dummy · gap 3

lead 4 · trail 1 · gap 3

lead 5 · trail 2 · gap 3

lead 6 · trail 3 · gap 3

lead 7 · trail 4 · gap 3

lead 8 · trail 5 · gap 3

lead 8 · trail 5 · gap 3

lead 8 · trail 5 · gap 3

lead 8 · trail 5 · gap 3

lead None · trail 6 · result [1, 2, 3, 4, 5, 6, 8]

result [1, 2, 3, 4, 5, 7, 8]

Each circle is a node, and D is the dummy node. Arrows are next links. A removed node disappears from the picture.

  • lead the pointer that moves first and stops on the last node
  • trail the pointer that stays n steps behind lead
  • lead+trail both pointers on the same node, so the gap is 0
  • target the node that trail.next points at, which the code removes
  • new link the link that was just written

dummy.next = head, and lead and trail both start on the dummy node D. The gap is 0, and n is 3.

for _ in range(n): lead moves to node 1. trail stays on D, so the gap is 1.

for _ in range(n): lead moves to node 2. trail stays on D, so the gap is 2.

The last pass of range(n) moves lead to node 3. The gap is now 3, which equals n.

while lead.next: lead moves to node 4 and trail moves to node 1. The gap is still 3.

while lead.next: lead moves to node 5 and trail moves to node 2. The gap is still 3.

while lead.next: lead moves to node 6 and trail moves to node 3. The gap is still 3.

while lead.next: lead moves to node 7 and trail moves to node 4. The gap is still 3.

while lead.next: lead moves to node 8 and trail moves to node 5. The gap is still 3.

lead.next is None, so the loop stops. lead is on the last node, so trail is on node 5.

trail.next is node 6. It is the node number 3 from the end, and it is the target.

trail.next = trail.next.next links node 5 to node 7, and node 6 is gone.

With while lead: instead, lead runs on to None, and trail ends on node 6. trail.next.next removes node 7, and the result is [1, 2, 3, 4, 5, 6, 8].

return dummy.next gives the list [1, 2, 3, 4, 5, 7, 8]. Node 6, the node number 3 from the end, is removed.

Dlead+trail12345678
with while lead:
empty
return dummy.next
empty
1 / 14
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing while lead: instead of while lead.next:

    Mistake
    while lead:
    Fix
    while lead.next:

    What happens: The gap of n steps is sized for a loop that stops with lead on the last node. The test while lead: lets lead move one more node, to None. Then trail also moves one more node and stops on the node to remove, not on the node before it. The line trail.next = trail.next.next then removes the node after the right one. When n = 1, trail ends on the last node, trail.next is None, and trail.next.next raises an AttributeError.

    Example: head = [1, 2, 3, 4, 5], n = 2
    Mistake: returns [1, 2, 3, 4]Fix: returns [1, 2, 3, 5]
  2. 2

    Gotcha 2: Starting lead and trail on head instead of dummy

    Mistake
    lead = trail = head
    Fix
    lead = trail = dummy

    What happens: When n equals the length L, the node to remove is the head, and trail must stop on the node before it. Only dummy is before the head. If both pointers start on head, the first loop needs L steps from the first node, and the last step moves lead to None. Then lead.next in the second loop reads an attribute of None, and Python raises an AttributeError.

    Example: head = [1, 2], n = 2
    Mistake: raises AttributeErrorFix: returns [2]
  3. 3

    Gotcha 3: Returning head instead of dummy.next

    Mistake
    return head
    Fix
    return dummy.next

    What happens: head still points at the first node of the input. When n equals the length, the code removes that node by setting dummy.next to the second node. head does not change. Returning head returns a list that still starts with the removed node.

    Example: head = [1, 2], n = 2
    Mistake: returns [1, 2]Fix: returns [2]
  4. 4

    Gotcha 4: Expecting the fixed gap to use fewer pointer moves than counting the length

    What happens: Counting the length first moves one pointer L nodes. A second walk then moves L - n nodes to reach the node before the target. The fixed gap moves lead L nodes in total and trail L - n nodes. Both versions make the same number of moves, and both take O(L) time and O(1) extra space. The fixed gap does not need a first pass to learn L. One loop finds the node, which is the one-pass version that Remove Nth Node From End of List (LC 19) asks for in its follow-up.

Not this one if…
  • The target is the middle, a fixed fraction of the length, as in Middle of the Linked List (LC 876). fast moves two nodes while slow moves one, so the distance between them grows instead of staying at n. See Fast & Slow Pointers
  • You need a result about every element inside a window whose size the input fixes, as in Find All Anagrams in a String (LC 438). Here you need only the one node that sits n steps behind lead. See Fixed Window

Deep Dive

What this card adds to Fast & Slow Pointers

Fast & Slow Pointers moves two pointers at different speeds, so the distance between them grows. This card moves both pointers at the same speed, so the distance between them never changes.

The distance has a fixed size n. It is set once, before the main loop: lead moves n nodes while trail stays on dummy. After that, lead and trail move one node per pass, and the gap stays n.

A singly linked list has only next links and no stored length. So you cannot walk back from the end. The gap replaces the walk back: when lead reaches the end, trail is already n nodes behind it.

Tracing [1, 2, 3, 4, 5, 6, 7, 8] with n = 3

lead and trail both start on dummy. The loop for _ in range(n) runs three times, so lead ends on node 3 while trail stays on dummy. The gap is 3.

Then while lead.next runs five times. Each pass moves lead and trail by one node, and the gap stays 3. lead stops on node 8, the last node. trail stops on node 5.

trail.next is node 6, the third node from the end. The line trail.next = trail.next.next links 5 to 7, so the result is [1, 2, 3, 4, 5, 7, 8]. For Example 1 of Remove Nth Node From End of List (LC 19), [1, 2, 3, 4, 5] with n = 2, the same code returns [1, 2, 3, 5].

Why trail stops on the node before the target

Let L be the number of nodes, and count dummy as position 0. After the first loop, lead is on position n and trail is on position 0. The second loop runs until lead is on position L, which is L - n passes. So trail ends on position L - n.

Position L - n + 1 is the n-th node from the end. It is the node after trail, so trail.next = trail.next.next removes it. The code never computes L: the end of the list stops lead, and the gap places trail.

When n equals L, the second loop runs zero times, and trail stays on dummy. The target is the head, and it is removed by the same line. The head needs no special case, because dummy is a real node in front of it.

The trap, the cost and a related problem

The loop test and the gap size belong together. With while lead.next, lead stops on the last node, and the gap is n. With while lead, lead runs one node further, to None, and the gap must be n + 1. For the traced list and n = 3, while lead with the gap n removes node 7, and the result is [1, 2, 3, 4, 5, 6, 8]. The last step of the board shows this.

lead moves L nodes and trail moves L - n nodes, so the time is O(L). The extra memory is dummy, lead and trail, so the space is O(1). Counting the length first and then walking L - n nodes does the same number of moves. The fixed gap finds the node in one pass.

Rotate List (LC 61) uses the same gap. It first counts the nodes and replaces k with k % L, because k can be larger than L. Then lead moves k nodes ahead, and both pointers move until lead is on the last node. trail is then on the new tail. The code sets lead.next to the old head, the new head is trail.next, and trail.next becomes None.

60-Second Pitch

"I need the node n from the end, and the list stores no length. So I keep two pointers n nodes apart. Both start on a dummy node in front of the head. I move lead forward n times. Then I move lead and trail together while lead.next exists. The gap stays n. When lead is on the last node, trail is on the node just before the target, so trail.next = trail.next.next removes it. Because I start on the dummy node, removing the head works with the same line. I return dummy.next. The time is O(L) in one pass, and the space is O(1)."

Common Questions

What is the time and space complexity?

The time is O(L), where L is the number of nodes: lead moves L nodes and trail moves L - n nodes. The space is O(1): dummy, lead and trail.

Why use a dummy node?

The target can be the head, when n equals L. The head has no node before it, and the code needs the node before the target. dummy is that node, so the same removal line works for every target.

Is this faster than counting the length first?

Both are O(L). Counting the length moves one pointer L nodes, and the second walk moves L - n nodes. The fixed gap makes the same number of moves in a single loop, so the list is read once. It matches the one-pass follow-up of Remove Nth Node From End of List (LC 19).

What changes if n can be larger than the list length?

The first loop would move lead past the end, to None, and lead.next would fail. Check the length first, or stop the first loop when lead becomes None. Remove Nth Node From End of List (LC 19) promises that n is at most the length.

How is this different from Fast & Slow Pointers?

In Fast & Slow Pointers, fast moves two nodes while slow moves one, so the distance grows. It finds the middle or a cycle. Here both pointers move one node, so the distance stays n. It finds a node a fixed number of nodes from the end.

SWE Reasoning

Spot it

  • The problem names a node by its distance from the end of a singly linked list.
  • The list stores no length, and the problem asks for one pass or O(1) extra space.
  • The answer needs the node before the target, because you must change its next link.
  • Two positions in the list must stay a known number of nodes apart.

The invariant, step by step

  1. dummy = ListNode(0, head), and lead = trail = dummy. The gap is 0.
  2. for _ in range(n): lead = lead.next moves lead n nodes ahead. The gap is n.
  3. while lead.next moves lead and trail one node each. The gap is still n after every pass.
  4. The loop stops when lead is on the last node. trail is n nodes behind it, on the node before the target.
  5. trail.next = trail.next.next removes the target. When the target is the head, trail is dummy.
  6. return dummy.next returns the first node of the result.

Traps

  • while lead: with a gap of n. lead runs on to None, trail ends one node too far, and the node after the target is removed. With n = 1, trail.next.next raises an AttributeError.
  • Starting both pointers on head. When n equals L, no node is before the head, and the pointers cannot stop in the right place.
  • Returning head. If the head was removed, head still points at the removed node.
  • Moving trail during the first loop. The gap then stays 0 and the pointers never separate.

Practice

Canonical LeetCode:Remove Nth Node From End of List (LC 19)Rotate List (LC 61)Swapping Nodes in a Linked List (LC 1721)Intersection of Two Linked Lists (LC 160)
Theory Context•Two Pointers & Sliding Window

Builds on Fast & Slow Pointers, which you haven’t finished yet.

P2MediumBuilt on Fast & Slow Pointers~7 min speedrun

Fixed-Offset Pointers (Nth Node From End)

Built on
Fast & Slow PointersStart one pointer n steps ahead, then move both one step at a time, so the gap between them stays fixed.
The problem

Remove Nth Node From End of List (LC 19)(opens leetcode.com)

Your input is a linked list's head plus an integer n. Delete the node that sits n positions from the end of the list, then return the (possibly new) head.

Follow up: Can you find and delete that node in one pass, without first counting the list's length?

Example 1
Input:head = [1,2,3,4,5], n = 2
Output:[1,2,3,5]
The 2nd node from the end is 4. Removing it yields 1 -> 2 -> 3 -> 5.
12345

Fixed-Offset Pointers

list + n ➔ lead n steps ahead of trail ➔ move both together, trail stops n steps behind lead (Fixed Gap)
Spot it when: A singly linked list stores no length, and you must reach or remove the node that is n nodes from the end, in one pass with O(1) extra space.
Fixed-Offset Pointers
Python
1from typing import Optional
2 
3class ListNode:
4 def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
5 self.val = val
6 self.next = next
7 
8def remove_nth_from_end(head: Optional[ListNode], n: int) -> Optional[ListNode]:
9 """Remove Nth Node From End of List: keep lead n steps ahead of trail, so trail stops just before the target.
10 L = number of nodes in the list.
11 Time: O(L) | Space: O(1)
12 """
13 dummy = ListNode(0, head) # Every real node, the head too, now has a node before it
14 lead = trail = dummy # Both start on dummy, so lead is 0 steps ahead of trail
15 
16 # 1. Open The Gap:
17 # Moving lead n nodes forward while trail stays put leaves lead exactly n steps ahead of trail.
18 for _ in range(n):
19 lead = lead.next
20 
21 # 2. Move Both Pointers Together:
22 # Each pass moves lead and trail one node, so lead stays n steps ahead. lead stops on the last node, which puts trail on the node just before the n-th node from the end.
23 while lead.next:
24 lead = lead.next
25 trail = trail.next
26 
27 # 3. Unlink The Target:
28 # trail.next is the n-th node from the end. Skipping it removes it, and when it is the head, trail is dummy, so the head needs no special case.
29 trail.next = trail.next.next
30 
31 # 4. Return Through The Dummy, Not head:
32 # head itself may have been removed, so dummy.next, not head, is always the first node of the result.
33 return dummy.next
Invariant: After the first n steps, lead is exactly n steps ahead of trail, and every pass of the loop moves both by one node, so the gap is still n.

A singly linked list has no link back, so you cannot walk from its end. Move lead forward n nodes while trail stays on dummy. Now lead is n steps ahead of trail. Then move both one node per loop pass. When lead is the last node, trail is n steps behind it, on the node just before the one to remove.

The first loop sets the gap to n. Each pass of the second loop moves lead and trail by one node, so the gap stays n. For a list of L nodes, lead ends on node L and trail ends on node L - n. The node after trail is the n-th node from the end. The code never computes L. When n equals L, trail stays on dummy, so the head is removed like any other node.

The trap is the pair of the gap size and the loop test. The loop while lead.next stops lead on the last node, so the gap must be n, set with range(n). If you write while lead instead, lead runs one node further, to None, and the gap must be n + 1. Mixing the two leaves trail one node away from the right place, and the code removes the wrong node.

See it run

Remove Nth Node From End of List: remove_nth_from_end([1, 2, 3, 4, 5, 6, 7, 8], n = 3)

lead dummy · trail dummy · gap 0

lead 1 · trail dummy · gap 1

lead 2 · trail dummy · gap 2

lead 3 · trail dummy · gap 3

lead 4 · trail 1 · gap 3

lead 5 · trail 2 · gap 3

lead 6 · trail 3 · gap 3

lead 7 · trail 4 · gap 3

lead 8 · trail 5 · gap 3

lead 8 · trail 5 · gap 3

lead 8 · trail 5 · gap 3

lead 8 · trail 5 · gap 3

lead None · trail 6 · result [1, 2, 3, 4, 5, 6, 8]

result [1, 2, 3, 4, 5, 7, 8]

Each circle is a node, and D is the dummy node. Arrows are next links. A removed node disappears from the picture.

  • lead the pointer that moves first and stops on the last node
  • trail the pointer that stays n steps behind lead
  • lead+trail both pointers on the same node, so the gap is 0
  • target the node that trail.next points at, which the code removes
  • new link the link that was just written

dummy.next = head, and lead and trail both start on the dummy node D. The gap is 0, and n is 3.

for _ in range(n): lead moves to node 1. trail stays on D, so the gap is 1.

for _ in range(n): lead moves to node 2. trail stays on D, so the gap is 2.

The last pass of range(n) moves lead to node 3. The gap is now 3, which equals n.

while lead.next: lead moves to node 4 and trail moves to node 1. The gap is still 3.

while lead.next: lead moves to node 5 and trail moves to node 2. The gap is still 3.

while lead.next: lead moves to node 6 and trail moves to node 3. The gap is still 3.

while lead.next: lead moves to node 7 and trail moves to node 4. The gap is still 3.

while lead.next: lead moves to node 8 and trail moves to node 5. The gap is still 3.

lead.next is None, so the loop stops. lead is on the last node, so trail is on node 5.

trail.next is node 6. It is the node number 3 from the end, and it is the target.

trail.next = trail.next.next links node 5 to node 7, and node 6 is gone.

With while lead: instead, lead runs on to None, and trail ends on node 6. trail.next.next removes node 7, and the result is [1, 2, 3, 4, 5, 6, 8].

return dummy.next gives the list [1, 2, 3, 4, 5, 7, 8]. Node 6, the node number 3 from the end, is removed.

Dlead+trail12345678
with while lead:
empty
return dummy.next
empty
1 / 14
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing while lead: instead of while lead.next:

    Mistake
    while lead:
    Fix
    while lead.next:

    What happens: The gap of n steps is sized for a loop that stops with lead on the last node. The test while lead: lets lead move one more node, to None. Then trail also moves one more node and stops on the node to remove, not on the node before it. The line trail.next = trail.next.next then removes the node after the right one. When n = 1, trail ends on the last node, trail.next is None, and trail.next.next raises an AttributeError.

    Example: head = [1, 2, 3, 4, 5], n = 2
    Mistake: returns [1, 2, 3, 4]Fix: returns [1, 2, 3, 5]
  2. 2

    Gotcha 2: Starting lead and trail on head instead of dummy

    Mistake
    lead = trail = head
    Fix
    lead = trail = dummy

    What happens: When n equals the length L, the node to remove is the head, and trail must stop on the node before it. Only dummy is before the head. If both pointers start on head, the first loop needs L steps from the first node, and the last step moves lead to None. Then lead.next in the second loop reads an attribute of None, and Python raises an AttributeError.

    Example: head = [1, 2], n = 2
    Mistake: raises AttributeErrorFix: returns [2]
  3. 3

    Gotcha 3: Returning head instead of dummy.next

    Mistake
    return head
    Fix
    return dummy.next

    What happens: head still points at the first node of the input. When n equals the length, the code removes that node by setting dummy.next to the second node. head does not change. Returning head returns a list that still starts with the removed node.

    Example: head = [1, 2], n = 2
    Mistake: returns [1, 2]Fix: returns [2]
  4. 4

    Gotcha 4: Expecting the fixed gap to use fewer pointer moves than counting the length

    What happens: Counting the length first moves one pointer L nodes. A second walk then moves L - n nodes to reach the node before the target. The fixed gap moves lead L nodes in total and trail L - n nodes. Both versions make the same number of moves, and both take O(L) time and O(1) extra space. The fixed gap does not need a first pass to learn L. One loop finds the node, which is the one-pass version that Remove Nth Node From End of List (LC 19) asks for in its follow-up.

Not this one if…
  • The target is the middle, a fixed fraction of the length, as in Middle of the Linked List (LC 876). fast moves two nodes while slow moves one, so the distance between them grows instead of staying at n. See Fast & Slow Pointers
  • You need a result about every element inside a window whose size the input fixes, as in Find All Anagrams in a String (LC 438). Here you need only the one node that sits n steps behind lead. See Fixed Window

Deep Dive

What this card adds to Fast & Slow Pointers

Fast & Slow Pointers moves two pointers at different speeds, so the distance between them grows. This card moves both pointers at the same speed, so the distance between them never changes.

The distance has a fixed size n. It is set once, before the main loop: lead moves n nodes while trail stays on dummy. After that, lead and trail move one node per pass, and the gap stays n.

A singly linked list has only next links and no stored length. So you cannot walk back from the end. The gap replaces the walk back: when lead reaches the end, trail is already n nodes behind it.

Tracing [1, 2, 3, 4, 5, 6, 7, 8] with n = 3

lead and trail both start on dummy. The loop for _ in range(n) runs three times, so lead ends on node 3 while trail stays on dummy. The gap is 3.

Then while lead.next runs five times. Each pass moves lead and trail by one node, and the gap stays 3. lead stops on node 8, the last node. trail stops on node 5.

trail.next is node 6, the third node from the end. The line trail.next = trail.next.next links 5 to 7, so the result is [1, 2, 3, 4, 5, 7, 8]. For Example 1 of Remove Nth Node From End of List (LC 19), [1, 2, 3, 4, 5] with n = 2, the same code returns [1, 2, 3, 5].

Why trail stops on the node before the target

Let L be the number of nodes, and count dummy as position 0. After the first loop, lead is on position n and trail is on position 0. The second loop runs until lead is on position L, which is L - n passes. So trail ends on position L - n.

Position L - n + 1 is the n-th node from the end. It is the node after trail, so trail.next = trail.next.next removes it. The code never computes L: the end of the list stops lead, and the gap places trail.

When n equals L, the second loop runs zero times, and trail stays on dummy. The target is the head, and it is removed by the same line. The head needs no special case, because dummy is a real node in front of it.

The trap, the cost and a related problem

The loop test and the gap size belong together. With while lead.next, lead stops on the last node, and the gap is n. With while lead, lead runs one node further, to None, and the gap must be n + 1. For the traced list and n = 3, while lead with the gap n removes node 7, and the result is [1, 2, 3, 4, 5, 6, 8]. The last step of the board shows this.

lead moves L nodes and trail moves L - n nodes, so the time is O(L). The extra memory is dummy, lead and trail, so the space is O(1). Counting the length first and then walking L - n nodes does the same number of moves. The fixed gap finds the node in one pass.

Rotate List (LC 61) uses the same gap. It first counts the nodes and replaces k with k % L, because k can be larger than L. Then lead moves k nodes ahead, and both pointers move until lead is on the last node. trail is then on the new tail. The code sets lead.next to the old head, the new head is trail.next, and trail.next becomes None.

60-Second Pitch

"I need the node n from the end, and the list stores no length. So I keep two pointers n nodes apart. Both start on a dummy node in front of the head. I move lead forward n times. Then I move lead and trail together while lead.next exists. The gap stays n. When lead is on the last node, trail is on the node just before the target, so trail.next = trail.next.next removes it. Because I start on the dummy node, removing the head works with the same line. I return dummy.next. The time is O(L) in one pass, and the space is O(1)."

Common Questions

What is the time and space complexity?

The time is O(L), where L is the number of nodes: lead moves L nodes and trail moves L - n nodes. The space is O(1): dummy, lead and trail.

Why use a dummy node?

The target can be the head, when n equals L. The head has no node before it, and the code needs the node before the target. dummy is that node, so the same removal line works for every target.

Is this faster than counting the length first?

Both are O(L). Counting the length moves one pointer L nodes, and the second walk moves L - n nodes. The fixed gap makes the same number of moves in a single loop, so the list is read once. It matches the one-pass follow-up of Remove Nth Node From End of List (LC 19).

What changes if n can be larger than the list length?

The first loop would move lead past the end, to None, and lead.next would fail. Check the length first, or stop the first loop when lead becomes None. Remove Nth Node From End of List (LC 19) promises that n is at most the length.

How is this different from Fast & Slow Pointers?

In Fast & Slow Pointers, fast moves two nodes while slow moves one, so the distance grows. It finds the middle or a cycle. Here both pointers move one node, so the distance stays n. It finds a node a fixed number of nodes from the end.

SWE Reasoning

Spot it

  • The problem names a node by its distance from the end of a singly linked list.
  • The list stores no length, and the problem asks for one pass or O(1) extra space.
  • The answer needs the node before the target, because you must change its next link.
  • Two positions in the list must stay a known number of nodes apart.

The invariant, step by step

  1. dummy = ListNode(0, head), and lead = trail = dummy. The gap is 0.
  2. for _ in range(n): lead = lead.next moves lead n nodes ahead. The gap is n.
  3. while lead.next moves lead and trail one node each. The gap is still n after every pass.
  4. The loop stops when lead is on the last node. trail is n nodes behind it, on the node before the target.
  5. trail.next = trail.next.next removes the target. When the target is the head, trail is dummy.
  6. return dummy.next returns the first node of the result.

Traps

  • while lead: with a gap of n. lead runs on to None, trail ends one node too far, and the node after the target is removed. With n = 1, trail.next.next raises an AttributeError.
  • Starting both pointers on head. When n equals L, no node is before the head, and the pointers cannot stop in the right place.
  • Returning head. If the head was removed, head still points at the removed node.
  • Moving trail during the first loop. The gap then stays 0 and the pointers never separate.

Practice

Canonical LeetCode:Remove Nth Node From End of List (LC 19)Rotate List (LC 61)Swapping Nodes in a Linked List (LC 1721)Intersection of Two Linked Lists (LC 160)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•