When to use fast and slow pointers
Use fast and slow pointers when you have:
- A linked list and want its middle in one pass, without knowing its length.
- A question about whether a list or a chain of "next" links loops back on itself.
- A need to find where a loop starts, for example the first repeated node.
- A "follow the pointer" problem in disguise, such as find the duplicate number in an array of
n + 1numbers from 1 ton, or the happy number puzzle. - A strict limit of O(1) extra memory, which rules out remembering every node you have seen.
Use something else when:
- You can afford O(n) memory and want the simplest code: a hash set of visited nodes finds a loop at once.
- The data is an array that you can index: a normal two-pointer or a sliding window is easier.
- You need the kth node from the end: use a fixed gap between two pointers, not different speeds.
There is no library to import. It is two references and a loop. Linked lists also exist in every standard library (see the real-world section), but loop detection is something you write yourself.
How fast and slow pointers work
Start two pointers at the head of the list. Slow moves one node per step. Fast moves two. They never move backwards.
If the list has a loop, fast gains one node on slow every step inside the loop, so they must meet. If it has no loop, fast reaches the end first.
Why they must meet: once both are inside the loop, the gap between them changes by exactly one node each step, so it cannot jump past zero without landing on it. There is no way for fast to hop over slow.
There are three uses, all with the same two pointers:
- Find the middle. When fast reaches the end, slow has travelled half the distance, so it is at the middle. This takes one pass and no counting.
- Detect a loop. If slow and fast ever stand on the same node, the list loops.
- Find where the loop starts. After the pointers meet, put slow back at the head and move both one node at a time. They meet again exactly at the first node of the loop. The reason is that the distance from the head to the loop start equals the distance from the meeting point forward to the loop start, going round the loop.
The same idea works on an array. If nums[i] is "the next index to visit", the array is a linked list, and a repeated number makes two indexes point to the same place, which creates a loop.
Try it
Pick one of the three jobs, then press Run and step through. Slow is teal and fast is blue. A node holding both is orange, and green marks the answer.
Fast and slow pointers
Step 1 of 8 · Press Space to play, arrow keys to stepNode → next node: 1 → 2, 2 → 3, 3 → 4, 4 → 5, 5 → 6, 6 → 3
Node 6 points back to node 3, so the list loops. Both pointers start at the first node.
- moves:
- 0
- phase:
- 1: look for a loop
Slow is teal and fast is blue. A node holding both is orange and green marks the answer. The moves counter counts each pointer move.
Things to try:
- Find a loop, "Loop back to 3": the list
1 2 3 4 5 6where the last node points back to node 3. The pointers meet on node 5 after 4 moves, then 2 more moves find that the loop starts at node 3. Choose "No loop" and fast reaches the end instead. - Find the middle, "Seven nodes": slow stops on node 4 after 3 moves, without counting the list first. With "Six nodes" there are two middle nodes and the loop returns the second, node 4.
- Find the duplicate number: for
3 1 4 4 2 5the pointers meet after 2 moves, then 2 more moves show the duplicate is 4. Try1 3 4 2 2: the duplicate is 2.
Fast and slow pointer operations
Find the middle. Start both pointers at the head. While fast and the node after fast both exist, move slow one node and fast two. When the loop stops, slow is the middle (the second middle if the length is even).
Detect a loop. Use the same loop, and after each move check whether slow and fast are the same node. If they are, there is a loop. If the loop ends because fast ran out of nodes, there is none.
Find where the loop starts.
- Run loop detection until slow and fast meet.
- Move slow back to the head. Leave fast where it is.
- Move both one node at a time until they meet. That node is the start of the loop.
Interview fact. Fast moves two steps, but the whole thing is still O(n) and uses O(1) memory: neither pointer visits a node more than a few times.
Fast and slow pointer time complexity
| Job | Time | Extra memory |
|---|---|---|
| Find the middle | O(n), one pass | O(1) |
| Detect a loop | O(n) | O(1) |
| Find where the loop starts | O(n) | O(1) |
| Find the duplicate number in an array | O(n) | O(1), array not changed |
If there is a loop, slow never walks more than about one full trip round the list before they meet, so the pointer work is bounded by a small multiple of n.
Fast and slow pointers vs other approaches
n is the number of nodes.
| What you need | Fast and slow pointers | Hash set of visited nodes | Count the length first | Sort (arrays only) |
|---|---|---|---|---|
| Is there a loop | O(n) time, O(1) memory | O(n) time, O(n) memory | Cannot tell | Not applicable |
| Where does the loop start | O(n) time, O(1) memory | O(n) time, O(n) memory | Cannot tell | Not applicable |
| Middle of a list | One pass, O(1) memory | Not helpful | Two passes, O(1) memory | Not applicable |
Duplicate in n + 1 numbers | O(n) time, O(1) memory, array unchanged | O(n) time, O(n) memory | Not applicable | O(n log n) time, changes the array |
The bold cells show where each wins. Fast and slow pointers win on memory. A hash set is simpler to write when memory is not a worry, and it never gets the "why does it work" question in an interview.
How to explain fast and slow pointers in an interview
- Say what moves how fast: "slow moves one node, fast moves two".
- Give the reason they meet: "inside a loop fast gains one node per step, so it cannot skip over slow".
- For the loop start, say the second phase out loud: "I put slow back at the head and move both one step at a time".
- State both costs: O(n) time and O(1) memory, and offer the hash set as the simpler O(n) memory version.
- Mention the edge cases: an empty list, one node, a loop that starts at the head, and
fast.nextbeing null before you callfast.next.next.