Fast & Slow Pointers — The Complete Guide
Master the tortoise-and-hare technique from first principles. Learn to detect cycles, find midpoints, and identify entry points — all in O(1) space — and confidently solve interview questions that rely on pointer-speed asymmetry.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
Imagine you're walking along a path and you suspect it might loop back on itself. How do you prove it without marking the ground? You can't just keep walking — if there's a loop, you'll walk forever. And you can't mark nodes you've visited without extra memory. The fast-and-slow pointer technique solves this elegantly: send two walkers at different speeds. If there's a cycle, the fast one will eventually lap the slow one — they'll meet. If there's no cycle, the fast one reaches the end.
This is Floyd's Cycle Detection Algorithm, also called the tortoise-and-hare algorithm. The slow pointer moves one step at a time. The fast pointer moves two steps. That speed difference is the entire trick — it guarantees they'll meet inside any cycle, and it lets you find the middle of a list, detect cycles, locate cycle entry points, and more — all in O(1) extra space.
The pattern exists because linked lists don't have random access. You can't jump to the middle or check the length without traversing. Fast-slow pointers give you structural information about the list (middle, cycle, length) using only pointer manipulation — no hash sets, no arrays, no extra memory.
Analogies to Build Intuition
Tortoise and Hare on a Track
A tortoise and a hare start at the same point on a circular track. The hare runs twice as fast. No matter how big the track is, the hare will eventually lap the tortoise — they'll be at the same spot. If the track is straight (no loop), the hare reaches the end first. That meeting (or not meeting) tells you whether there's a cycle.
Clock Hands
The minute hand moves 12× faster than the hour hand. They start together at 12 and meet again roughly every 65 minutes. The speed difference guarantees periodic meetings. Fast-slow pointers work the same way — the speed gap forces a collision inside any cycle.
Finding the Middle of a Race
Two runners start a race together. One runs at normal speed, the other at double speed. When the fast runner finishes, the slow runner is exactly at the halfway point. That's how fast-slow pointers find the middle of a linked list — no need to know the length.
What kind of problems does it solve?
Fast-slow pointers are your go-to when:
- You need to detect a cycle in a linked list or sequence
- You need to find the start of a cycle (the entry node)
- You need the middle node of a linked list without knowing its length
- You need to check if a linked list is a palindrome in O(1) space
- You need to detect cycles in number sequences (Happy Number, finding duplicates)
- You want O(1) space — no hash sets or visited arrays
The core insight
When two pointers move at different speeds through a structure, the speed difference creates a predictable relationship between their positions. In a cycle, they must meet. In a straight path, the fast pointer reaches the end when the slow pointer is at the middle. This speed asymmetry is the entire foundation of the pattern.
Pattern Recognition
Recognizing when fast-slow pointers apply is the most important skill. Here are the signals to look for and the traps to avoid.
Keywords & Signals in the Problem Statement
Use Fast-Slow Pointers when you see...
- ✅"Detect if a linked list has a cycle"
- ✅"Find the start/entry of the cycle"
- ✅"Find the middle node of a linked list"
- ✅"Check if a linked list is a palindrome" (O(1) space)
- ✅"Happy number" — detect repeating sequences
- ✅"Find the duplicate number" in [1, n] with n+1 elements
- ✅"Reorder list" — needs the middle to split
- ✅O(1) space constraint on a linked list problem
- ✅Any problem where a sequence might loop back on itself
- ✅"Kth node from the end" — variant with fixed gap
Don't use Fast-Slow Pointers when...
- ❌You need to find pairs that sum to a target — use Two Pointers on sorted data
- ❌The data is an array with random access — direct indexing is simpler
- ❌You need to process a contiguous subarray — use Sliding Window
- ❌You need to detect duplicates in general — use Hashing (O(n) space is fine)
- ❌The structure has no next-pointer semantics (no linked list, no function sequence)
- ❌You need to visit all nodes — use BFS/DFS instead
Fast-Slow Pointers vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Fast-Slow Pointers | Cycle detection, finding midpoints, O(1) space linked list ops | Two pointers at different speeds; exploits speed asymmetry |
| Two Pointers (converging) | Sorted arrays, pair sum, container with most water | Pointers move toward each other from opposite ends |
| Two Pointers (same direction) | Remove duplicates, partition, sliding window | Both move forward but at different conditions (not different speeds) |
| Hashing (for cycle detection) | When O(n) space is acceptable | Store visited nodes in a set; simpler but uses O(n) space |
| Linked List reversal | Palindrome check, reorder list | Often combined with fast-slow: find middle, then reverse second half |
The fast-slow question to always ask
When you see a linked list problem with an O(1) space constraint, or any problem where a sequence might cycle, ask: "Can I use two pointers at different speeds?" If the structure has next-pointer semantics (linked list, or a function that maps values to next values), fast-slow pointers almost certainly apply.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using the most classic fast-slow problem: Linked List Cycle Detection — given a linked list, determine if it has a cycle.
Brute Force — Hash Set of Visited Nodes
Traverse the list and store every node in a hash set. If you encounter a node that's already in the set, there's a cycle. If you reach null, there's no cycle. Correct, but uses O(n) extra space.
function hasCycleBrute(head: ListNode | null): boolean { const visited = new Set<ListNode>(); let current = head; while (current !== null) { if (visited.has(current)) return true; // cycle! visited.add(current); current = current.next; } return false; // reached null — no cycle } // Problem: O(n) space for the hash set. // Can we detect the cycle without storing every node?
Optimal — Floyd's Fast-Slow Pointers
Use two pointers: slow moves 1 step, fast moves 2 steps. If there's a cycle, fast will eventually catch up to slow (they'll meet). If there's no cycle, fast reaches null. Zero extra space.
function hasCycle(head: ListNode | null): boolean { let slow = head; let fast = head; while (fast !== null && fast.next !== null) { slow = slow!.next; // 1 step fast = fast.next.next; // 2 steps if (slow === fast) return true; // they met — cycle! } return false; // fast reached null — no cycle } // O(n) time: fast traverses at most 2n nodes before meeting slow. // O(1) space: only two pointers, no hash set. // Why do they meet? In a cycle, fast gains 1 node per step on slow. // So the gap shrinks by 1 each step → guaranteed collision.
Why Does the Optimization Work?
Speed difference guarantees meeting
Once both pointers are inside the cycle, fast gains exactly 1 node on slow per step (fast moves 2, slow moves 1, gap closes by 1). If the cycle has length C, they'll meet within C steps. No matter how big the cycle is.
No cycle means fast hits null
If the list is acyclic, fast reaches the end (null) before slow. The while condition (fast !== null && fast.next !== null) catches this cleanly. No infinite loop.
The general principle
Whenever you need to detect a cycle in a structure with 'next' semantics, two pointers at different speeds will collide inside the cycle. This replaces O(n) space (hash set) with O(1) space (two pointers). The same principle extends to finding cycle starts, midpoints, and more.
The thinking process matters more than the code
In interviews, walk through this progression: "The brute force uses a hash set to track visited nodes — O(n) space. But if I use two pointers at different speeds, the fast one will lap the slow one inside any cycle. They must meet because the gap closes by 1 each step. O(1) space." This shows the interviewer you understand the math behind the technique.
Core Templates
Fast-slow pointer problems follow a few recurring templates. Memorize these — most problems are variations of one of them.
Template 1: Cycle Detection
Detect whether a linked list (or any sequence with next-pointer semantics) contains a cycle. Slow moves 1 step, fast moves 2 steps. If they meet, there's a cycle.
function hasCycle(head: ListNode | null): boolean { let slow = head; let fast = head; while (fast !== null && fast.next !== null) { slow = slow!.next; fast = fast.next.next; if (slow === fast) return true; } return false; } // When to use: "does this linked list have a cycle?" // Also works for number sequences: next = f(current) // Key: check fast AND fast.next before advancing
Template 2: Find Cycle Start (Floyd's Phase 2)
After detecting a cycle (pointers meet), find where the cycle begins. Reset one pointer to head, keep the other at the meeting point, then advance both at the same speed. They meet at the cycle start.
function detectCycleStart(head: ListNode | null): ListNode | null { let slow = head; let fast = head; // Phase 1: Detect cycle (find meeting point) while (fast !== null && fast.next !== null) { slow = slow!.next; fast = fast.next.next; if (slow === fast) { // Phase 2: Find cycle start let entry = head; while (entry !== slow) { entry = entry!.next; slow = slow!.next; } return entry; // cycle start node } } return null; // no cycle } // Why does Phase 2 work? // Let D = distance from head to cycle start // Let K = distance from cycle start to meeting point // At meeting: slow traveled D + K, fast traveled D + K + nC // Since fast = 2 × slow: D + K + nC = 2(D + K) → D = nC - K // So moving D steps from head AND from meeting point both reach cycle start. // When to use: "find where the cycle begins", // "find the duplicate number" (array as linked list)
Template 3: Find Middle Node
Find the middle of a linked list in one pass. When fast reaches the end, slow is at the middle. For even-length lists, this gives the first of the two middle nodes (or the second, depending on the loop condition).
// Returns the FIRST middle (for even-length lists) function middleNode(head: ListNode | null): ListNode | null { let slow = head; let fast = head; while (fast?.next !== null && fast?.next?.next !== null) { slow = slow!.next; fast = fast.next.next; } return slow; } // Returns the SECOND middle (for even-length lists) function middleNodeSecond(head: ListNode | null): ListNode | null { let slow = head; let fast = head; while (fast !== null && fast.next !== null) { slow = slow!.next; fast = fast.next.next; } return slow; } // 1 → 2 → 3 → 4 → 5: both return node 3 (odd length) // 1 → 2 → 3 → 4 → 5 → 6: first returns 3, second returns 4 // // When to use: "find the middle", "split list in half", // prerequisite for palindrome check and reorder list
Template 4: Linked List Palindrome (Composite Pattern)
Check if a linked list is a palindrome in O(1) space. This combines three techniques: find middle (fast-slow), reverse second half, compare both halves. It's a classic composite pattern.
function isPalindrome(head: ListNode | null): boolean { if (!head || !head.next) return true; // Step 1: Find middle (slow ends at first middle) let slow: ListNode | null = head; let fast: ListNode | null = head; while (fast?.next && fast?.next?.next) { slow = slow!.next; fast = fast.next.next; } // Step 2: Reverse second half let prev: ListNode | null = null; let curr = slow!.next; while (curr) { const next = curr.next; curr.next = prev; prev = curr; curr = next; } // Step 3: Compare first half and reversed second half let left = head; let right = prev; while (right) { if (left!.val !== right.val) return false; left = left!.next; right = right.next; } return true; } // Time: O(n) — three passes (find middle, reverse, compare) // Space: O(1) — only pointer manipulation // When to use: "is this linked list a palindrome?" with O(1) space
Which template to pick?
Ask yourself: (1) Do I need to detect a cycle? → Template 1. (2) Do I need to find where the cycle starts? → Template 2. (3) Do I need the middle of the list? → Template 3. (4) Do I need to check palindrome in O(1) space? → Template 4 (which uses Template 3 as a building block).
Variations & Sub-Patterns
Fast-slow pointers aren't limited to basic cycle detection. Here are the most common variations and how the approach adapts for each.
| Variation | Pointer Strategy | Classic Example |
|---|---|---|
| Cycle Detection | Slow +1, fast +2; meet → cycle exists | Linked List Cycle |
| Cycle Start | After meeting, reset one to head; advance both +1 until they meet | Linked List Cycle II, Find the Duplicate Number |
| Find Middle | Slow +1, fast +2; when fast ends, slow is at middle | Middle of the Linked List, Sort List |
| Palindrome Check | Find middle → reverse second half → compare halves | Palindrome Linked List |
| Cycle in Number Sequence | Treat f(x) as 'next pointer'; apply Floyd's on the sequence | Happy Number, Find the Duplicate Number |
| Reorder List | Find middle → reverse second half → interleave | Reorder List |
| Kth from End (fixed gap) | Advance fast K steps first, then move both +1 until fast ends | Remove Nth Node From End of List |
| Cycle Length | After meeting, keep one still, advance the other until they meet again; count steps | Find cycle length (sub-step in many problems) |
Problems mentioned above
Deep Dive: Happy Number — Cycle in a Number Sequence
A number is "happy" if repeatedly replacing it with the sum of the squares of its digits eventually reaches 1. If it never reaches 1, it enters a cycle. This is Floyd's algorithm applied to a number sequence instead of a linked list — the "next pointer" is the digit-square-sum function.
function isHappy(n: number): boolean { function getNext(num: number): number { let sum = 0; while (num > 0) { const digit = num % 10; sum += digit * digit; num = Math.floor(num / 10); } return sum; } let slow = n; let fast = getNext(n); while (fast !== 1 && slow !== fast) { slow = getNext(slow); // 1 step fast = getNext(getNext(fast)); // 2 steps } return fast === 1; } // If the sequence reaches 1, fast gets there first → return true. // If the sequence cycles, slow and fast meet → return false. // Key insight: any bounded sequence that doesn't terminate must cycle. // Digit square sums are bounded (e.g., 999 → 243), so it must cycle or reach 1.
Deep Dive: Find the Duplicate Number — Array as Linked List
Given an array of n+1 integers where each integer is in [1, n], find the duplicate. The brilliant insight: treat the array as a linked list where nums[i] is the "next pointer" from index i. A duplicate value means two indices point to the same node — that's a cycle. Use Floyd's to find the cycle start.
function findDuplicate(nums: number[]): number { // Treat array as linked list: index → nums[index] // A duplicate means two indices map to the same value → cycle // Phase 1: Detect cycle (find meeting point) let slow = nums[0]; let fast = nums[0]; do { slow = nums[slow]; // 1 step fast = nums[nums[fast]]; // 2 steps } while (slow !== fast); // Phase 2: Find cycle start (= the duplicate value) let entry = nums[0]; while (entry !== slow) { entry = nums[entry]; slow = nums[slow]; } return entry; } // Time: O(n) — Floyd's algorithm // Space: O(1) — no extra data structures // Why it works: the duplicate value is the entry point of the cycle. // Two different indices point to it → it's where paths converge.
Deep Dive: Reorder List — Composite Pattern
Reorder a list from L0 → L1 → ... → Ln to L0 → Ln → L1 → Ln-1 → ... This combines three fast-slow sub-patterns: find middle, reverse second half, then merge/interleave the two halves.
function reorderList(head: ListNode | null): void { if (!head || !head.next) return; // Step 1: Find middle let slow: ListNode | null = head; let fast: ListNode | null = head; while (fast?.next && fast?.next?.next) { slow = slow!.next; fast = fast.next.next; } // Step 2: Reverse second half let prev: ListNode | null = null; let curr = slow!.next; slow!.next = null; // cut the list while (curr) { const next = curr.next; curr.next = prev; prev = curr; curr = next; } // Step 3: Interleave (merge alternating) let first = head; let second = prev; while (second) { const tmp1 = first!.next; const tmp2 = second.next; first!.next = second; second.next = tmp1; first = tmp1; second = tmp2; } } // Time: O(n) — three linear passes // Space: O(1) — only pointer manipulation // This is the "composite pattern" — find middle + reverse + merge. // Many linked list problems decompose into these three building blocks.
Visual Walkthrough
Let's trace through the core fast-slow algorithms step by step to see exactly how the pointers move and why they meet.
Cycle Detection — Step by Step
List: 1 → 2 → 3 → 4 → 5 → 3 (cycle back to node 3) Positions (node values): Step 0: slow=1, fast=1 (start) Step 1: slow=2, fast=3 (slow +1, fast +2) Step 2: slow=3, fast=5 (slow +1, fast +2) Step 3: slow=4, fast=4 (slow +1, fast +2 → 3→4) slow === fast → CYCLE DETECTED ✓ Why they met at node 4: - After entering the cycle, fast gains 1 position per step on slow - Cycle length = 3 (nodes 3, 4, 5) - Fast was 2 ahead of slow in the cycle → met after 1 more step - They ALWAYS meet within one full cycle length
Finding Cycle Start — The Math Behind Phase 2
List: 1 → 2 → 3 → 4 → 5 → 3 (cycle starts at node 3) D = distance from head to cycle start = 2 (nodes 1, 2) C = cycle length = 3 (nodes 3, 4, 5) Phase 1: Find meeting point slow traveled: D + K = 2 + 1 = 3 steps (at node 4) fast traveled: 2(D + K) = 6 steps (also at node 4) K = distance from cycle start to meeting point = 1 Phase 2: Find cycle start Reset entry pointer to head (node 1) Keep slow at meeting point (node 4) Step 1: entry=2, slow=5 (both move +1) Step 2: entry=3, slow=3 (both move +1) entry === slow → CYCLE START = node 3 ✓ Why Phase 2 works: D = nC - K (from the math: fast = 2 × slow) So D steps from head = D steps from meeting point Both land on the cycle start. In our example: D=2, C=3, K=1 → D = 3-1 = 2 ✓
Finding Middle — Step by Step
Odd length: 1 → 2 → 3 → 4 → 5 Step 0: slow=1, fast=1 Step 1: slow=2, fast=3 Step 2: slow=3, fast=5 fast.next is null → STOP Middle = slow = node 3 ✓ (exact middle) Even length: 1 → 2 → 3 → 4 → 5 → 6 "Second middle" version (while fast && fast.next): Step 0: slow=1, fast=1 Step 1: slow=2, fast=3 Step 2: slow=3, fast=5 Step 3: slow=4, fast=null (fast was 5, fast.next=6, fast.next.next=null) Wait — fast=5, fast.next=6, so we continue: Step 3: slow=4, fast=null (6.next = null, so fast becomes null) Middle = slow = node 4 ✓ (second of two middles) "First middle" version (while fast.next && fast.next.next): Step 0: slow=1, fast=1 Step 1: slow=2, fast=3 Step 2: slow=3, fast=5 fast.next=6, fast.next.next=null → STOP Middle = slow = node 3 ✓ (first of two middles) Key: The loop condition determines which middle you get for even-length lists.
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of the fast-slow pointer technique. Solve them in order.
LC 141. Linked List Cycle
Why this pattern:
Pure cycle detection (Template 1). Slow moves 1 step, fast moves 2 steps. If they meet, there's a cycle. If fast reaches null, there isn't. The most fundamental fast-slow problem — solve this first to build the core intuition.
Key idea: Initialize both pointers at head. Loop while fast and fast.next are not null. Advance slow by 1, fast by 2. If slow === fast, return true. If the loop ends, return false.
LC 876. Middle of the Linked List
Why this pattern:
Find middle (Template 3). When fast reaches the end, slow is at the middle. This problem asks for the second middle node for even-length lists, so use the 'while fast && fast.next' loop condition.
Key idea: Both start at head. Advance slow by 1, fast by 2. When fast is null or fast.next is null, slow is at the middle. For even-length lists, this gives the second middle (node 4 in a 6-node list).
LC 202. Happy Number
Why this pattern:
Cycle detection on a number sequence (Template 1 adapted). The 'next pointer' is the digit-square-sum function. If the sequence reaches 1, it's happy. If it cycles without hitting 1, it's not. This teaches you that fast-slow works on ANY sequence with next-pointer semantics, not just linked lists.
Key idea: Define getNext(n) = sum of squares of digits. Initialize slow = n, fast = getNext(n). Loop: slow = getNext(slow), fast = getNext(getNext(fast)). If fast === 1, return true. If slow === fast, return false (cycle detected).
LC 142. Linked List Cycle II
Why this pattern:
Find cycle start (Template 2 — Floyd's Phase 2). First detect the cycle using fast-slow. Then reset one pointer to head and advance both at speed 1. They meet at the cycle entry. This is the key problem for understanding the math behind Floyd's algorithm.
Key idea: Phase 1: detect cycle (slow and fast meet). Phase 2: reset entry = head. Advance entry and slow both by 1 until they meet. The meeting point is the cycle start. The math: D = nC - K, so D steps from head equals D steps from meeting point.
LC 234. Palindrome Linked List
Why this pattern:
Composite pattern (Template 4). Find middle using fast-slow, reverse the second half, then compare both halves node by node. This combines three techniques: find middle + reverse + compare. It's the gateway to understanding how fast-slow is a building block for larger algorithms.
Key idea: Step 1: find middle (slow at first middle). Step 2: reverse from slow.next onward. Step 3: compare head→...→middle with reversed second half. If all values match, it's a palindrome. O(1) space because reversal is in-place.
LC 143. Reorder List
Why this pattern:
Composite pattern: find middle + reverse second half + interleave. Same building blocks as palindrome check, but the merge step interleaves instead of comparing. This cements the 'find middle → reverse → merge' decomposition.
Key idea: Step 1: find middle, cut the list. Step 2: reverse the second half. Step 3: interleave nodes from first and reversed-second halves alternately. Three linear passes, O(1) space.
LC 287. Find the Duplicate Number
Why this pattern:
Floyd's cycle detection on an array treated as a linked list (Template 2). The array index is the 'node', nums[i] is the 'next pointer'. A duplicate value means two indices point to the same node — creating a cycle. The duplicate is the cycle entry point.
Key idea: Treat the array as a linked list: start at index 0, follow nums[i] as next. Phase 1: slow = nums[slow], fast = nums[nums[fast]] until they meet. Phase 2: reset entry to 0, advance both by 1 until they meet. The meeting point is the duplicate value.
Practice strategy
For each problem: (1) Identify which template applies before coding. (2) Draw the pointer positions for a small example. (3) After solving, write one sentence: "Fast-slow works here because [reason]." This builds the pattern-matching reflex for interviews.
Common Mistakes
These are the traps that catch people on fast-slow pointer problems. Learn them here so you don't learn them in an interview.
Null pointer crash on fast.next.next
You advance fast by 2 steps without checking that fast.next exists. If fast is at the last node, fast.next.next crashes with a null reference error.
✅Always check BOTH conditions: while (fast !== null && fast.next !== null). This ensures fast.next.next is safe. The order matters — check fast first, then fast.next.
Wrong loop condition for finding middle
You use the wrong while condition and get the wrong middle node for even-length lists. 'while (fast && fast.next)' gives the second middle; 'while (fast.next && fast.next.next)' gives the first middle.
✅Know which middle you need. For palindrome check and reorder list, you typically want the first middle (to split evenly). For LeetCode 876 (Middle of Linked List), the problem asks for the second middle. Write a comment stating which one you're targeting.
Forgetting Phase 2 of Floyd's algorithm
You detect the cycle (Phase 1) but then try to return the meeting point as the cycle start. The meeting point is NOT the cycle start — it's just some node inside the cycle.
✅After Phase 1 (pointers meet), you MUST do Phase 2: reset one pointer to head, keep the other at the meeting point, advance both by 1 until they meet. That meeting point is the cycle start. Don't skip Phase 2.
Not cutting the list when splitting at the middle
You find the middle node but forget to set middle.next = null to actually split the list into two halves. The second half still points back to the first, causing infinite loops or wrong results.
✅After finding the middle, save middle.next as the head of the second half, then set middle.next = null. This cleanly separates the two halves. Essential for palindrome check, reorder list, and sort list.
Starting fast at getNext(head) instead of head
For number sequence problems (Happy Number), you start fast at getNext(n) but for linked list problems you start both at head. Mixing these up causes off-by-one errors or missed cycles.
✅For linked lists: both start at head, use a while loop. For number sequences: slow = n, fast = getNext(n), use a while loop (or do-while). The difference is because linked list comparison is by reference (same node), while number comparison is by value.
Not understanding the math behind Phase 2
You memorize the algorithm but can't explain WHY resetting to head and advancing both by 1 finds the cycle start. When the interviewer asks, you're stuck.
✅Understand the proof: slow travels D + K, fast travels 2(D + K) = D + K + nC. So D + K = nC, meaning D = nC - K. Moving D steps from head and D steps from the meeting point (which is K past the cycle start) both land on the cycle start. Practice explaining this in one sentence.
Interview Strategy
Knowing the algorithm is half the battle. Here's how to present fast-slow pointer solutions in an interview to maximize your score.
The 5-Step Interview Flow
Clarify & Spot the Structure
'This is a linked list — can it have a cycle? Is there a space constraint?' or 'The array has n+1 elements in range [1,n] — that means there must be a duplicate, and I can treat it as a linked list.' — Identify the next-pointer semantics.
State the Brute Force
'I could use a hash set to track visited nodes — O(n) space. But since we need O(1) space, I'll use Floyd's cycle detection with two pointers at different speeds.' — Show you know the tradeoff.
Explain the Speed Asymmetry
'Slow moves 1 step, fast moves 2 steps. Inside a cycle, fast gains 1 position per step on slow, so they must meet within one cycle length. If there's no cycle, fast reaches null.' — This is the key insight interviewers want to hear.
Code It Clean
Write the while condition carefully (check fast AND fast.next). Use clear variable names (slow, fast, entry). If the problem needs Phase 2, add a comment explaining the math: 'D = nC - K, so D steps from head meets D steps from meeting point.'
Test & Analyze
Trace through a small example: 'For 1→2→3→4→5→3: slow goes 1,2,3,4 and fast goes 1,3,5,4 — they meet at 4. Phase 2: entry starts at 1, slow at 4. Both advance: (2,5), (3,3) — meet at 3, the cycle start.' State: O(n) time, O(1) space.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "Why do they meet? Prove it." | Inside the cycle, fast gains 1 node per step on slow. If the gap is G, they meet in G steps. Since G < C (cycle length), they always meet within one full traversal of the cycle. |
| "Why does Phase 2 find the cycle start?" | Slow traveled D + K. Fast traveled 2(D + K). The difference is D + K = nC, so D = nC - K. Moving D steps from head and D steps from the meeting point (K past cycle start) both reach the cycle start. |
| "Can you find the cycle length?" | After Phase 1 (pointers meet), keep one pointer still and advance the other one step at a time until they meet again. Count the steps — that's the cycle length. |
| "What if the fast pointer moves 3 steps instead of 2?" | It still works for cycle detection (they'll meet), but the math for Phase 2 changes. Speed 2 is standard because the gap closes by exactly 1 per step, making the proof clean and the meeting guaranteed within C steps. |
| "Can you restore the list after checking palindrome?" | Yes — after comparing, reverse the second half again to restore the original list. This is good practice and shows attention to side effects. Mention it even if the interviewer doesn't ask. |
| "How does Find the Duplicate work without modifying the array?" | Treat index as node, nums[index] as next pointer. Floyd's algorithm only reads the array — never writes. The duplicate is the cycle entry point because two indices map to the same value. |
The meta-skill
Interviewers love fast-slow pointer problems because they test mathematical reasoning, not just coding. The ability to explain WHY the pointers meet and WHY Phase 2 finds the cycle start separates strong candidates from those who just memorized the code. Practice the one-sentence explanation: "Fast gains 1 per step, so they meet in at most C steps. Then D = nC - K means D steps from head equals D steps from the meeting point."
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: Two pointers at different speeds exploit speed asymmetry to detect cycles, find midpoints, and locate entry points — all in O(1) space
Cycle detection: Slow +1, fast +2. If they meet → cycle. If fast hits null → no cycle. Fast gains 1 per step inside the cycle
Cycle start (Phase 2): After meeting, reset one to head. Both advance +1. They meet at the cycle entry. Math: D = nC - K
Find middle: Slow +1, fast +2. When fast ends, slow is at middle. Loop condition determines first vs second middle
Palindrome check: Find middle → reverse second half → compare halves. Three passes, O(1) space
Number sequences: Any function f(x) creates a sequence. If bounded, it must cycle or terminate. Apply Floyd's with next = f(current)
Array as linked list: Index = node, nums[index] = next pointer. Duplicate value = cycle. Floyd's finds the duplicate as cycle entry
Null safety: Always check: while (fast !== null && fast.next !== null). This prevents null pointer crashes on fast.next.next
Composite pattern: Many problems combine: find middle + reverse + merge/compare. Master each building block separately
Space advantage: Hash set cycle detection: O(n) space. Floyd's: O(1) space. Same O(n) time. The space saving is the whole point
Cycle length: After meeting, keep one still, advance the other until they meet again. Count steps = cycle length
Mental trigger: "Linked list + O(1) space" or "detect cycle" or "find middle" or "sequence that might loop" → Fast-Slow Pointers
Decision Flowchart
Does the problem involve a linked list or a sequence with "next" semantics? ├── NO → Fast-slow probably doesn't apply. │ Consider Two Pointers, Sliding Window, or Hashing. └── YES → Do you need to detect a cycle? ├── YES → Is O(1) space required? │ ├── YES → Floyd's Cycle Detection (Template 1) │ └── NO → Hash Set is simpler (but O(n) space) └── NO → Do you need the cycle START? ├── YES → Floyd's Phase 1 + Phase 2 (Template 2) └── NO → Do you need the MIDDLE of the list? ├── YES → Fast-Slow Find Middle (Template 3) └── NO → Do you need palindrome check / reorder? ├── YES → Composite: middle + reverse + merge (Template 4) └── NO → Do you need Kth from end? ├── YES → Fixed-gap two pointers (advance fast K first) └── NO → Consider other linked list techniques
Fast-Slow Pointer Complexity Reference
| Problem | Time | Space | Template |
|---|---|---|---|
| Cycle Detection | O(n) | O(1) | Template 1 — basic Floyd's |
| Cycle Start | O(n) | O(1) | Template 2 — Floyd's Phase 1 + 2 |
| Find Middle | O(n) | O(1) | Template 3 — fast ends, slow at middle |
| Palindrome Check | O(n) | O(1) | Template 4 — middle + reverse + compare |
| Reorder List | O(n) | O(1) | Composite — middle + reverse + interleave |
| Happy Number | O(log n) | O(1) | Template 1 on number sequence |
| Find Duplicate | O(n) | O(1) | Template 2 on array-as-linked-list |
One last thing
Fast-slow pointers are one of the most elegant patterns in all of DSA. The entire technique rests on one simple idea: when two things move at different speeds through a cycle, they must eventually collide. From that single insight, you get cycle detection, cycle start finding, midpoint location, palindrome checking, and duplicate finding — all in O(1) space. Master the math behind Phase 2 (D = nC - K), and you'll be able to explain and adapt this pattern to any variant an interviewer throws at you.