14 Fast and Slow Pointer LeetCode Problems, Ordered by What They Teach

- Two templates to master first: 2:1 speed detection (LC 141) and equal-speed fixed-gap (LC 19) look similar but solve different problems
- Functional graph abstraction (LC 202, LC 287): the "pointer" is any deterministic function, not a
.nextfield; the[1,n]constraint makes every array value a valid index - Derive
a = kL - bonce (LC 142): memorizing the phase-2 step without the math means you'll misapply it on any variant - Initialization determines which middle you get:
fast = headyields the second middle,fast = head.nextyields the first; the wrong choice silently breaks downstream steps - LC 457 adds per-step validity constraints: direction checks and self-loop rejection go at every pointer advance, and marking processed nodes keeps the algorithm O(n)
- LC 143 and LC 148 are composition problems: the fast-slow step is one of three; failures usually happen in the step before or after it
- LC 143 is the pattern checkpoint: if you can narrate and implement all three operations cleanly, you have enough command to handle any fast-slow interview problem
You solved LC 141. Cycle detection. Fast pointer laps the slow one, they collide if there's a cycle, you return True. Four minutes, felt great, closed the tab.
Then LC 876 shows up asking you to find the middle of a linked list. You start typing slow, fast = head, head and your hand hovers. Is that right? Which middle does this give me for an even-length list? Does it matter for the next step?
It matters. The fast/slow pointer pattern has exactly two core templates, one initialization choice that completely changes behavior, and fourteen problems worth doing in a specific order. This is that order.
For the underlying math (why the pointers always meet, how to find the cycle start), see Floyd's Cycle Detection Algorithm and Finding the Cycle Start. For the five recognition signals that tell you when to reach for the pattern at all, read Fast-Slow Pointer Problems. This article assumes you have the template and focuses on building intuition through the right sequence.
The 14 Problems at a Glance
| # | Problem | Difficulty | Core Lesson |
|---|---|---|---|
| 1 | Linked List Cycle (LC 141) | Easy | The detection template |
| 2 | Middle of the Linked List (LC 876) | Easy | Initialization and first vs. second middle |
| 3 | Happy Number (LC 202) | Easy | Abstract cycles beyond linked lists |
| 4 | Linked List Cycle II (LC 142) | Medium | Two-phase algorithm and the phase-2 proof |
| 5 | Remove Nth Node From End (LC 19) | Medium | Equal-speed fixed-gap variant |
| 6 | Delete the Middle Node (LC 2095) | Medium | Stopping slow one position early |
| 7 | Palindrome Linked List (LC 234) | Easy | Middle finding as a subproblem |
| 8 | Swapping Nodes in a Linked List (LC 1721) | Medium | kth from start and kth from end |
| 9 | Find the Duplicate Number (LC 287) | Medium | Array values as pointer indices |
| 10 | Circular Array Loop (LC 457) | Medium | Direction constraints on cycle detection |
| 11 | Reorder List (LC 143) | Medium | Three-operation composition |
| 12 | Maximum Twin Sum (LC 2130) | Medium | Symmetric access after splitting at middle |
| 13 | Rotate List (LC 61) | Medium | Circular list then break |
| 14 | Sort List (LC 148) | Medium | Fast/slow as a recursive divide step |

1. Linked List Cycle (LC 141)
What it teaches: The detection template in its cleanest form.
def hasCycle(head): slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next if slow is fast: # identity, not equality return True return False
Two things to internalize before moving on. First, the guard is fast and fast.next, not just fast. The second check handles lists with an odd number of nodes where fast's next-next would dereference None. Second, the check is is, not ==. For object references, == calls __eq__, which may not test identity.
Write this from scratch, without looking, three times. Everything else builds on it.
Yes, three times. Not two. The third time is when your hands stop thinking and start knowing.
2. Middle of the Linked List (LC 876)
What it teaches: One character of initialization changes which middle you get.
# Second middle (what this problem asks for): fast starts at head slow, fast = head, head # First middle (useful for palindrome): fast starts one step ahead slow, fast = head, head.next
For an even-length list there are two middle nodes. fast = head gives you the second one. fast = head.next gives the first. The difference matters in every problem that splits the list and then does work on both halves. Get the wrong one and the second half is one node too long, and the merge step crashes.
One character. The whole algorithm breaks. Welcome to linked list problems.
3. Happy Number (LC 202)
What it teaches: The "linked list" doesn't have to exist in memory.
def digit_square_sum(n): total = 0 while n: n, d = divmod(n, 10) total += d * d return total def isHappy(n): slow, fast = n, n while True: slow = digit_square_sum(slow) fast = digit_square_sum(digit_square_sum(fast)) if fast == 1: return True if slow == fast: return False
The sequence of digit-square-sums is deterministic and operates in a bounded domain. That means it either reaches 1 or enters a cycle. Fast/slow detects which.
This is the conceptual unlock: "next" is any deterministic function, not a .next pointer. Any functional graph with bounded state has cycles. Once this clicks, LC 287 feels inevitable. It won't feel inevitable the first time. Stick with it.
4. Linked List Cycle II (LC 142)
What it teaches: How to find where the cycle starts, not just that one exists.
Phase 1 is identical to LC 141. Find the meeting point. Then:
slow = head while slow is not fast: slow = slow.next fast = fast.next return slow
The reason this works: if the cycle starts at distance a from head and the meeting point is distance b into the cycle, then fast has traveled 2(a + b) = a + b + kL. Solving: a = kL - b. Advancing both pointers at speed 1 from head and from the meeting point, they cover the same distance to the cycle start.
Don't memorize this. Derive a = kL - b once, and the code follows directly. Memorizing the phase-2 step without the proof means you'll misapply it the moment the problem looks slightly different.
5. Remove Nth Node From End (LC 19)
What it teaches: The equal-speed fixed-gap variant. Not 2:1 speed.
dummy = ListNode(0, head) slow, fast = dummy, dummy for _ in range(n + 1): fast = fast.next while fast: slow = slow.next fast = fast.next slow.next = slow.next.next
Fast advances n + 1 steps (not n) so that when fast hits None, slow is on the node before the target, not on it. The dummy node eliminates the edge case of removing the first node. The pattern here is equal speed with a maintained offset, not 2:1 speed.
Two different templates, both called "fast and slow pointers." Know which one you're reaching for before you start typing.
6. Delete the Middle Node (LC 2095)
What it teaches: Shifting slow's starting position to land before the target.
dummy = ListNode(0, head) slow, fast = dummy, head while fast and fast.next: slow = slow.next fast = fast.next.next slow.next = slow.next.next
Starting slow at a dummy node rather than head shifts its final position back by one. When fast finishes, slow points to the node before the middle. Any time you need to delete or modify a node using the pointer that stops before it, start slow one step early. The dummy node is the one-step-early trick.
7. Palindrome Linked List (LC 234)
What it teaches: Fast/slow as one step inside a multi-step solution.
Approach: find the first middle with fast = head.next, reverse the second half, compare node by node. The fast/slow part is ten lines. The whole problem is twenty.
The common failure isn't the pattern. It's using fast = head instead of fast = head.next, which makes the first half longer than the second for even-length lists, and the comparison loop reads one extra node. The initialization error is invisible until you trace through a four-node example.
Trace through a four-node example. Seriously.
8. Swapping Nodes in a Linked List (LC 1721)
What it teaches: Finding kth from start and kth from end in one pass.
first = head for _ in range(k - 1): first = first.next # kth node from start second, fast = head, first while fast.next: second = second.next fast = fast.next # gap maintained, second lands on kth from end first.val, second.val = second.val, first.val
After locating kth from start, reset a second pointer at head and advance both until fast is at the tail. The gap stays k positions, so when fast can't go further, second is at kth from end. Swap values, not node pointers. It saves relinking work and the result is identical.
9. Find the Duplicate Number (LC 287)
What it teaches: Translating a constraint into a functional graph.
Constraints: n + 1 numbers in [1, n]. Every value is a valid index. Define next(i) = nums[i]. Starting from index 0, follow this "pointer." The duplicate value is the one two indices map to, making it the cycle entry in the functional graph.
slow, fast = 0, 0 while True: slow = nums[slow] fast = nums[nums[fast]] if slow == fast: break slow = 0 while slow != fast: slow = nums[slow] fast = nums[fast] return slow
while True is safe here: a duplicate is guaranteed, so a cycle is guaranteed, so phase 1 terminates. This isn't reckless optimism. It's a proof.
This problem is LC 142 with a different data representation. The [1, n] constraint is what makes every value a valid index. Internalize that transformation and you'll spot similar setups in problems that look nothing like linked lists.
10. Circular Array Loop (LC 457)
What it teaches: Cycle detection with validity constraints at every step.
Not every cycle counts. A valid cycle must have length greater than 1, and every element must point in the same direction (all positive or all negative).
def nxt(nums, i): n = len(nums) return ((i + nums[i]) % n + n) % n # Check direction consistency at every advance if nums[i] * nums[nxt(nums, i)] < 0: break # direction changed, no valid cycle through here
The self-loop check (nxt(i) == i) rejects length-1 cycles. The sign check rejects direction-mixed cycles. Both checks go at every pointer advance, not just at the start. After processing a starting index that leads to no valid cycle, mark those nodes (set to 0) so future iterations skip them. This brings worst-case behavior from O(n²) to O(n).
LC 457 is the most paranoid version of this pattern. If you can handle it cleanly, the constraints in everything else look manageable.
11. Reorder List (LC 143)
What it teaches: Full three-operation composition.
The target structure L0 → Ln → L1 → Ln-1 → ... requires:
- Find the middle via fast/slow (use
fast = head.nextfor the first middle) - Reverse the second half in place
- Merge the two halves interleaved
None of the three operations is hard on its own. The composition is where candidates fail. The off-by-one from step 1 propagates to step 3 and causes a null dereference on the last merge iteration.
Trace through a six-node example before coding. This is one place where "I'll just run it and fix the bugs" becomes twenty minutes of confusion in front of an interviewer.
This problem is a checkpoint. If you can do it cleanly, you have enough command of the pattern to handle any interview problem that uses it.
12. Maximum Twin Sum of a Linked List (LC 2130)
What it teaches: Symmetric access after a midpoint split.
# Find middle slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next # Reverse second half prev, curr = None, slow while curr: curr.next, prev, curr = prev, curr, curr.next # Scan from both ends simultaneously result, left, right = 0, head, prev while right: result = max(result, left.val + right.val) left = left.next right = right.next return result
Structurally, this is the palindrome check problem. The only difference is that the comparison produces a maximum sum rather than a boolean. Recognizing the structural similarity is the skill the problem is actually testing.
13. Rotate List (LC 61)
What it teaches: The make-circular-then-break pattern.
length, tail = 1, head while tail.next: length += 1 tail = tail.next tail.next = head # circular k = k % length if k == 0: tail.next = None return head steps = length - k for _ in range(steps): tail = tail.next new_head = tail.next tail.next = None return new_head
Connecting tail to head first simplifies the pointer work. The k % length reduction handles the case where k is a multiple of the list length, which would produce unnecessary work and a subtle off-by-one. The circular-then-break pattern generalizes to any problem that needs to wrap around a list.
14. Sort List (LC 148)
What it teaches: Fast/slow as the split step in a recursive divide-and-conquer.
def sortList(head): if not head or not head.next: return head slow, fast = head, head.next # first middle: shorter right half while fast and fast.next: slow = slow.next fast = fast.next.next mid = slow.next slow.next = None return merge(sortList(head), sortList(mid))
Every recursive level calls fast/slow once to find the midpoint. The fast = head.next initialization ensures the left sublist is never longer than the right, preventing infinite recursion on two-node inputs. This problem makes the O(n log n) cost visible: finding the middle is O(n) work done at each of O(log n) levels.
The bottom-up approach avoids the O(log n) recursion stack by merging sublists of size 1, 2, 4, 8, ... iteratively, but the fast/slow split stays the conceptual heart.
What All 14 Fast-Slow Pointer Problems Share
The fast and slow pointer pattern is about using pointer-relative position to encode information you would otherwise need extra space to store. Cycle presence, cycle start, list midpoint, kth-from-end position, sorted merge boundaries: all fall out of where the pointers land.
Problems 1 through 4 establish the two core templates: detection (2:1 speed) and gap (equal speed, fixed offset). Problems 5 through 10 show the variants and the functional graph abstraction. Problems 11 through 14 require combining the pattern with other operations, which is the level interviews actually test.
If you want to practice narrating your way through pattern recognition in real time (not just solving silently in a text editor), SpaceComplexity runs voice-based DSA mock interviews with rubric feedback across exactly the dimensions these problems target.
Key Takeaways
- LC 141 and LC 19 are the two templates. Learn both. They look similar and work differently.
- LC 202 and LC 287 teach the abstraction. The "linked list" is a function, not a data structure.
- LC 142 requires deriving
a = kL - b. Memorizing the phase-2 step without the proof means you'll misapply it when the problem is slightly varied. - LC 457 is the generalization with constraints. Every pointer advance needs validation, not just the final result.
- LC 143 and LC 148 are composition problems. The fast/slow part is one step. Failing them usually means failing the step before or after.
Further Reading
- Floyd's cycle-finding algorithm (Wikipedia, full mathematical proof)
- LeetCode 141: Linked List Cycle (canonical problem, editorial covers complexity analysis)
- LeetCode 287: Find the Duplicate Number (editorial covers the functional graph approach in detail)
- LeetCode 457: Circular Array Loop (most complex variant, editorial walks through direction constraint edge cases)
- Knuth, TAOCP Volume 2 (original attribution context; Knuth credited Floyd in 1969, which is how the algorithm got its name despite Floyd never publishing it)