Floyd's cycle detection
Find out whether following links ever loops back, and where the loop starts, using a fast and a slow pointer and no extra memory.
O(n)Space O(1)4 practice problemsStep-through pictureThe idea
Sometimes a chain of links loops back: the last node's next points to a node you already passed, so a walk along the chain never ends. This is called a cycle. A simple check remembers every node in a set and stops when it sees one twice, but the set can grow as big as the list.
Floyd's method, also called the tortoise and hare, uses two pointers instead. slow moves one node per step and fast moves two. If there is no cycle, fast reaches the end. If there is a cycle, both pointers end up going round the loop, and fast gains one node on slow every step, so it must land on slow after a while.
A second phase finds where the loop starts: put a new pointer, finder, at the head and move it and slow one node at a time. They meet exactly at the first node of the loop.
An everyday example
Two runners on a track shaped like the number 6
A path leads from the park gate onto a circular track. Two runners start at the gate, one twice as fast as the other. If the path had a dead end, the fast runner would reach it and stop. But the path joins a circle, so both runners end up lapping it, and since the fast one gains a little every second, it eventually catches up with the slow one from behind.
| In the example | In the code |
|---|---|
| The park gate | head |
| The circular track | the cycle |
| The slow runner | slow = slow.next |
| The fast runner | fast = fast.next.next |
| The fast runner catches the slow one | slow === fast |
From the slow way to floyd's cycle detection
- Start with the obvious way to notice a loop: remember every node you visit in a
Set, and stop when you meet a node that is already in it. It works and even finds the loop's start, which is the first repeat. But the set grows with every node, so it needsO(n)extra memory. - Without memory, you need a different signal. Here is one: a walker inside a loop can never leave it. If two walkers move at different speeds and both end up in the loop, the faster one keeps gaining on the slower one, like runners lapping a track. If there is no loop, the faster one simply reaches the end.
- So move
slowone node andfasttwo nodes per step. Inside the loop, the distance fromfastback toslowshrinks by exactly one each step, so it can't skip past zero: they must land on the same node. That answers "is there a cycle?" with two pointers. But the meeting node is usually not where the loop starts. - A little counting fixes that. When they meet,
slowhas taken a number of steps that is a whole number of laps of the loop. So a new pointer,finder, starting at the head, andslow, starting from the meeting node, reach the loop's first node at the same moment if both move one step at a time. That two-phase loop is Floyd's cycle detection.
How it works
- Start
slowandfastathead. - While
fastandfast.nextexist, moveslowone node andfasttwo nodes. - If
fastreaches the end, there is no cycle: returnnull. - If
slow === fast, they met inside the cycle. - Put
finderathead. Movefinderandslowone node at a time until they are the same node. - That node is where the cycle starts: return it.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function findCycleStart(head) {let slow = head;let fast = head;while (fast !== null && fast.next !== null) {slow = slow.next;fast = fast.next.next;if (slow === fast) {let finder = head;while (finder !== slow) {finder = finder.next;slow = slow.next;}return finder;}}return null;}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(1) |
Before meeting, slow takes fewer than n steps, because fast catches it within one lap of the loop. The second phase takes at most n more steps. Only three pointers are kept, unlike a visited set that stores every node.
- Best case
O(n)No cycle at all:
fastfalls off the end after aboutn / 2loops and phase two never runs. - Average case
O(n)A cycle somewhere in the middle: phase one takes at most
a + csteps, which is at mostn, and phase two takesamore. - Worst case
O(n)A long path into a tiny loop, such as the last node linking to itself. On 10 nodes, phase one takes 9 steps and phase two takes 9 more, still under
2nin total.
Common mistakes
- Comparing values (
slow.val === fast.val) instead of nodes. Different nodes can hold equal values, which gives false cycles. - Checking
slow === fastbefore moving them. They both start athead, so the check is true right away. - Moving
finderandslowat different speeds in phase two. Both must move exactly one node per step. - Starting
finderat the meeting point andslowat the head, then moving only one of them. Start one at the head, keep the other at the meeting point, and move both.
When to use it
Look for these clues in a problem:
- The question asks whether following
nextlinks, redirects, or "go to" rules ever repeats. - Each item points to exactly one next item, like an array where
nums[i]tells you where to go next. - The task asks for the first item that is part of a loop.
- The task asks for
O(1)extra space, so a set of visited nodes isn't allowed.
Check yourself
Answer each one in your head first, then open it.
Why compare nodes with === instead of comparing val?
Two different nodes can hold the same value. Only the same node object means the pointers really met, so comparing values can report a cycle that isn't there.
Why must slow and fast move before the first comparison?
They both start at head, so comparing first would say they met at once. The code moves them and then checks.
If the path before the loop has 5 nodes, how many steps does phase two take?
Exactly 5. finder needs 5 steps to reach the loop's first node, and slow arrives there at the same moment.
Can phase one take more than n steps?
No. slow enters the loop after a steps and is caught within c more, and a + c is the number of nodes, n.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 4 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
If there is no cycle, fast reaches the end and the function returns null, which is right. If there is a cycle, call the path before it a nodes long and the loop c nodes long. slow enters the loop after a steps, and fast is already inside. From then on, the distance from fast forward to slow around the loop drops by exactly one per step, so they meet within at most c more steps.
At the meeting, slow has taken s steps and fast has taken 2s, from the same start. The extra s steps that fast took were spent going round the loop, so s is a whole number of laps: a multiple of c. In the trace, s = 4 and c = 4.
Now finder starts at the head and slow at the meeting node, and both move one step at a time. After a steps, finder is at the loop's first node. slow has then taken s + a steps from the head in total: a steps to reach the loop, plus whole laps. So it is at the loop's first node too.
They can't meet earlier. Before a steps, finder is still on the path outside the loop, while slow is always inside it, so they are on different nodes. The first node they share is the loop's start, which is what the function returns.
Going deeper
3 more ideas
Why the two pointers must meet
Once both pointers are inside the loop, think of the distance from slow forward to fast around the loop. Each step slow moves one node and fast moves two, so that distance shrinks by exactly one. It can't jump over zero, so after at most one lap it becomes zero and they stand on the same node.
Why the second walk finds the start
Say the path before the loop has a nodes and the loop has c nodes. When they meet, slow has taken s steps and fast has taken 2s. The extra s steps were whole laps for fast, so s is a multiple of c. Now finder starts at the head and slow at the meeting point, and both move one node at a time. After a steps finder reaches the loop's start. slow has then taken s + a steps from the head in total, which is a steps plus whole laps, so it is also at the loop's start. They meet there for the first time.
It works on arrays too
If next[i] gives the index of the item after item i, the array is a linked list in disguise. Use indexes as the pointers: slow = next[slow] and fast = next[next[fast]]. This finds a repeated number in an array of n + 1 values from 1 to n, and it detects loops in any "go to the next state" rule, such as repeatedly summing the squares of a number's digits.
Work it by hand
Every step in a table, with why
A six-node list whose last node links back to the third. The path before the loop has 2 nodes and the loop has 4.
head = 8 -> 15 -> 4 -> 23 -> 16 -> 42, and 42.next = 4
| Step | Phase | slow | fast | finder | What happens | Why |
|---|---|---|---|---|---|---|
| 1 | 1 | 8 | 8 | - | Both start at head | No check yet: they are equal at the start, so the code moves them before comparing. |
| 2 | 1 | 15 | 4 | - | slow !== fast | fast jumped 8 -> 15 -> 4 and is already inside the loop. slow is still on the path before it. |
| 3 | 1 | 4 | 16 | - | slow !== fast | slow enters the loop at 4. fast is two nodes ahead of it, which means it is two nodes behind it going round the loop of 4. |
| 4 | 1 | 23 | 4 | - | slow !== fast | fast went 16 -> 42 -> 4, wrapping round the loop. It is now one node behind slow. |
| 5 | 1 | 16 | 16 | - | slow === fast: meet | The gap closed to zero. slow took 4 steps, exactly one lap of the 4-node loop. |
| 6 | 2 | 16 | 16 | 8 | finder starts at head | Phase two: finder goes to the head, slow stays at the meeting node, and fast is no longer used. |
| 7 | 2 | 42 | 16 | 15 | finder !== slow | Both move one node. finder is still on the path; slow is going round the loop. |
| 8 | 2 | 4 | 16 | 4 | finder === slow: return | After 2 steps, the length of the path before the loop, finder arrives at 4, and so does slow. |
It returns the node 4, where the loop starts, after 4 steps in phase one and 2 in phase two. No set was needed: the whole search kept three pointers.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Floyd's cycle detection | O(n) | O(1) | You must find a loop without extra memory, or without changing the nodes. |
| Set of visited nodes | O(n) | O(n) | Memory is not a concern and you want the simplest code. The first node seen twice is the loop's start. |
| Mark visited nodes in place | O(n) | O(1) | You are allowed to change the nodes, for example by setting a seen flag on each, and to clean up after. |
Extra memory: O(1): three pointers, slow, fast, and finder, compared with O(n) for a set of visited nodes.
How the steps grow
- Floyd's cycle detection, Set of visited nodes, Mark visited nodes in place
O(n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Floyd's cycle detection | 10 | 1,000 | 1 million |
| Set of visited nodes | 10 | 1,000 | 1 million |
| Mark visited nodes in place | 10 | 1,000 | 1 million |
Inputs that need care
5 tricky inputs
- An empty list:
head = null fast !== nullfails at once, so the loop never runs and the function returnsnull: no cycle.- One node that links to itself
- After one step
slowandfastare both that node, so they meet.finderstarts at the head, which is alreadyslow, so phase two takes zero steps and returns the head. - The whole list is the loop: the last node links to the head
- With
a = 0, they meet after exactlycsteps, back at the head, and phase two takes zero steps. The answer is the head. On a five-node ring, phase one takes 5 steps. - Repeated values, like
5 -> 5 -> 5with no cycle - The code compares nodes with
===, not values, so equal values never cause a false meeting. It correctly returnsnull. - An array where
next[i]is the index to visit next - Use indexes as pointers:
slow = next[slow]andfast = next[next[fast]]. Every index must point inside the array, or the walk readsundefined.
Variations and follow-ups
Twists interviewers add
Only ask whether a cycle exists
Stop at the first slow === fast and return true, or return false when fast reaches the end. Phase two is only needed when you want the loop's first node.
Measure the loop's length
After the meeting, keep fast still and move slow one node at a time, counting, until it comes back to fast. The count is c, the number of nodes in the loop.
Find the repeated number in an array
An array of n + 1 numbers, each from 1 to n, can be read as links: from index i go to index nums[i]. A repeated number means two indexes link to the same place, which is a loop's start. Floyd's method finds it in O(n) time without changing the array or using extra memory.
Brent's method
Instead of moving both pointers every step, keep one fixed and move the other, and teleport the fixed one forward whenever the step count hits a power of two. It finds the loop's length and start with fewer calls to next, which helps when computing the next item is expensive.
In an interview
Spot it, explain it, answer follow-ups
- Spot it from "does it ever loop?", "find where the cycle begins", or "an array where each value points to an index", especially with "
O(1)extra space" or "don't change the input". - Explain phase one with the shrinking gap: "inside the loop,
fastgains exactly one node onslowper step, so it can't jump over it." That one sentence answers "why must they meet?". - Explain phase two with laps: "
slowhas taken a whole number of laps when they meet, so afteramore steps it is at the loop's start, the same placefinderreaches afterasteps from the head." - Test by hand on a list with no cycle, a one-node self loop, and a list whose loop starts at the head. They cover the
nullreturn and the zero-step phase two. - Expect the follow-up "why not move
fastthree steps at a time?". It still finds a cycle, but the gap shrinks by two per step, so it can jump past zero and need extra laps before the pointers land on the same node. The phase-two math also assumesfasttook exactly twice as many steps asslow.
Where frontend code uses it
The same idea in real UI code
- A router or CDN config can redirect
/ato/band/bback to/a. Following each rule to its next one is a linked chain, and a cycle check stops an infinite redirect loop. - When you walk up a chain of parent links, such as
parentNodein a hand-built tree or a nested comment'sparentId, bad data can make the chain loop. Cycle detection lets the code stop instead of freezing the tab. - Bundlers report circular imports (file
aimportsb, which importsa). When each module has one import to follow, the check is exactly this; with many imports it becomes graph cycle detection.