Fast and Slow Pointer Technique: What It Is and How to Use It

- Fast and slow pointers move through the same structure at different speeds; a cycle guarantees they meet without allocating extra memory
- Cycle detection (LeetCode 141) runs in O(n) time and O(1) space; the gap between pointers shrinks by one per step, so fast can never jump over slow
- Finding the midpoint of a linked list uses the same loop; when fast hits the end, slow is at the center with no length calculation needed
- Happy Number (LeetCode 202) applies the technique to a number sequence; any deterministic next-value function can produce a cycle
- Find Duplicate (LeetCode 287) reframes an array as an implicit linked list; values in [1, n] act as pointers, and a duplicate creates the cycle entry point
- Three recognition signals: O(1) space on a linked list problem, any sequence that might loop forever, and array values bounded by array length
Someone hands you a linked list and asks: does this thing loop forever? You think for three seconds, open a hash set, visit every node, and the moment you see a repeat you've found the cycle. It works. It's clean. You feel good about it.
Then the interviewer raises an eyebrow and says "can you do it in O(1) space?"
Of course they do.
That's where the fast and slow pointer technique comes in. Two pointers. Different speeds. No extra memory. Once you internalize the intuition, you'll spot it in problems that don't even mention cycles.
The Core Idea
Send two pointers into the data structure at different speeds. The slow pointer moves one step at a time. The fast pointer moves two. If there's a cycle, the fast pointer will lap the slow one and they'll meet inside it.
Think of two runners on a circular track. One runs twice as fast as the other. No matter where they start, the faster runner eventually catches the slower one. They cannot miss each other. That's the whole thing.
If there's no cycle, the fast pointer hits null first. You return false, close your laptop, maybe hydrate.
Detecting a Cycle
Here's the canonical implementation for LeetCode 141:
def hasCycle(head: ListNode) -> bool: slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: return True return False
The loop condition is fast and fast.next. If fast is null, the list has ended. If fast.next is null, the next step would crash trying to access fast.next.next. Python short-circuits left-to-right, so the order matters. Yes, even though it's "just" Python.
Check slow == fast after moving, not before. Both pointers start at head. Checking before the first move would return True on every non-empty list, which would make this the most confidently wrong solution ever written.
This runs in O(n) time and O(1) space. You never allocate anything.
Why Fast Always Catches Slow
Once both pointers enter the cycle, consider the gap between them. Say slow has just entered and fast is already d steps ahead inside the cycle. Each iteration: slow moves forward one step, fast moves two. The gap shrinks by exactly one per round.
This is the part people get wrong. The gap doesn't shrink by two. It shrinks by one, because fast only gains one step on slow per iteration. So the countdown goes: d, d-1, d-2, ..., 1, 0.
Fast can never skip over slow. Skipping would require the gap to jump from 1 to -1 in a single step, which would mean fast somehow traveled three nodes in one move. It doesn't. The gap hits zero and they stop there.

The gap shrinks by exactly one per iteration. Fast will always land on slow, never fly past it.
Since d is at most L-1 (less than the cycle length), they meet before slow completes even one full lap inside the cycle.
Finding the Middle of a Linked List
Cycle detection is the famous use case. The exact same two-speed idea solves a completely different problem: finding the midpoint of a list you haven't measured.
When fast reaches the end, slow is at the middle. No length calculation. No second pass.
def middleNode(head: ListNode) -> ListNode: slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next return slow
For an odd-length list, slow lands on the center node. For even-length, it lands on the second of the two middle nodes. Finding the middle isn't usually the goal. It's step one of something larger, like Reorder List, where you split the list, reverse the second half, and interleave. Getting the split point wrong by one node breaks everything downstream, which is a fun way to spend forty-five minutes.
The N-Apart Variant
The fast/slow idea has a close cousin: spacing the two pointers a fixed distance apart instead of moving at different speeds. This is what appears in "Remove the Nth Node from the End of a Linked List."
Move fast n steps ahead. Then advance both one step at a time until fast reaches the end. Slow is now right before the target.
def removeNthFromEnd(head: ListNode, n: int) -> ListNode: 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 return dummy.next
Same one-pass, O(1) space structure. Different spacing logic. The dummy node handles deleting the head without special-casing, because nobody wants a special case for the head. When you see "from the end" in a linked list problem, this variant is almost always the move.
The Happy Number Problem
The pattern generalizes beyond linked lists entirely. Consider Happy Number (LeetCode 202): a number is happy if repeatedly replacing it with the sum of its squared digits eventually reaches 1. Unhappy numbers cycle forever through the same sequence.
No linked list. But there is a function that maps each number to a next number. That's sufficient.
def isHappy(n: int) -> bool: def next_num(n: int) -> int: total = 0 while n: digit = n % 10 total += digit * digit n //= 10 return total slow, fast = n, n while True: slow = next_num(slow) fast = next_num(next_num(fast)) if fast == 1: return True if slow == fast: return False
If the sequence reaches 1, it's happy. If slow and fast meet anywhere else, it's stuck in a cycle and the number is not happy. Neither is the person who encounters it.
The moment a problem says "this process might loop forever," fast and slow are worth considering. Any deterministic function from a value to its successor can be treated like a linked list. No actual nodes required.
Finding the Duplicate Number
LeetCode 287 is the most surprising application. You have an array of n+1 integers where each value is in [1, n]. Because values fit inside the valid index range, each value can point to another index. The array silently becomes an implicit linked list.
If there's a duplicate, two different indices both point to the same next index. That's a cycle by definition.
def findDuplicate(nums: list[int]) -> int: slow, fast = nums[0], nums[0] while True: slow = nums[slow] fast = nums[nums[fast]] if slow == fast: break slow = nums[0] while slow != fast: slow = nums[slow] fast = nums[fast] return slow
Phase 1 finds where slow and fast meet inside the cycle. Phase 2 finds the cycle entry point, which is the duplicate.
Here's why. Let a = distance from the array start to the cycle entry, b = distance from the cycle entry to the meeting point, L = cycle length. When they meet, slow has traveled a + b steps and fast has traveled 2(a + b). The extra distance fast traveled is a multiple of L: 2(a + b) - (a + b) = nL, so a + b = nL, which means a = nL - b. In Phase 2, slow starts at index 0 (distance a from the entry) and fast stays at the meeting point (distance nL - b from the entry, which also equals a going forward around the cycle). Both advance one step at a time and meet after exactly a steps, at the cycle entry.
This is O(n) time, O(1) space, and doesn't modify the input. If that feels like cheating, that's because it kind of is, and it's great. The full derivation is in the Floyd's cycle detection algorithm article, and finding where a cycle starts covers Phase 2 specifically.
When to Use the Fast and Slow Pointer Technique
The pattern has three main entry signals.
"O(1) space" on a linked list problem. If the problem asks for constant space, fast/slow is almost always the approach. A hash set works but costs O(n) memory, which is apparently unacceptable to the interviewer even though it costs nothing on any real machine.
"Does this sequence terminate?" Any process that might cycle, whether it's a linked list, a number sequence, or a state machine, can be modeled as a graph where you're checking for loops.
Values bounded by array length. When array values are in [1, n] and the array has n+1 elements, you can model it as a linked list. Array problems become cycle problems.
One non-obvious case: "find the midpoint." Not a cycle problem at all, but the same mechanics get you there in one pass without computing the length first.
Common Mistakes
The most frequent bug: forgetting to check fast.next before accessing fast.next.next.
# Crashes on any list with an odd number of nodes while fast: slow = slow.next fast = fast.next.next # fast.next might be None here
Always write while fast and fast.next. The fast check guards against null. The fast.next check guards against the two-step dereference crashing on the second-to-last node. Both conditions earn their place.
The second mistake: comparing values instead of identities. Cycle detection requires finding the same node in memory, not two nodes that happen to store the same value. In Python, ListNode objects don't define __eq__, so == checks object identity by default and you get away with it. In Java or C++, be deliberate about reference equality before you end up with a cycle detector that doesn't detect cycles.
Third: using the wrong midpoint for even-length lists. while fast and fast.next lands slow on the second middle node. while fast.next and fast.next.next lands it on the first. Neither is wrong in isolation, but the choice affects algorithms that split the list afterward. Pick the condition that matches what your downstream logic expects, and pick it on purpose rather than by accident.
Practice Problems
The technique covers more ground than most people realize. The 14 fast and slow pointer problems article covers the full problem set ordered by what each one teaches. Start with LeetCode 141, then 876, then 142. Those three cover the vast majority of what appears in real interviews.
Knowing the implementation is necessary but not sufficient. Interviewers want to hear why you chose this approach, what the complexity tradeoffs are versus the hash set alternative, and how you know fast can't skip over slow. If you want to rehearse explaining this out loud, SpaceComplexity runs voice-based mock interviews with rubric feedback on your communication, not just your code. Cycle detection problems come up constantly, and the narration matters as much as the solution.
For broader two-pointer context, the two pointer technique article covers converging pointers and other variants.