How do you detect a cycle in a linked list?
Use Floyd's tortoise and hare method. Move a slow pointer one step and a fast pointer two steps at a time. If the fast pointer reaches null, there is no cycle. If the two pointers ever meet, there is a cycle. It runs in O(n) time with O(1) extra space.
Explain it simply
Two friends go for a run. One jogs, the other runs twice as fast. If the track is a straight road, the fast runner simply reaches the end first and they never meet again. If the track is a circular ground, the fast runner will eventually come round from behind and catch up with the jogger.
A linked list with a cycle is the circular ground: some node's next points back to an earlier node, so walking the list never ends. Floyd's method sends a slow pointer and a fast pointer and checks whether they meet.
Line by line
function hasCycle(head) {
let slow = head;
let fast = head;
// fast moves two steps, so check both fast and fast.next
while (fast !== null && fast.next !== null) {
slow = slow.next; // one step
fast = fast.next.next; // two steps
if (slow === fast) {
return true; // they met inside a loop
}
}
return false; // fast fell off the end, so the list ends
}
// Build 1 -> 2 -> 3 -> 4 -> 5
function build(values) {
const nodes = values.map((val) => ({ val, next: null }));
for (let i = 0; i < nodes.length - 1; i++) nodes[i].next = nodes[i + 1];
return nodes;
}
const straight = build([1, 2, 3, 4, 5]);
console.log(hasCycle(straight[0])); // Output: false
const looped = build([1, 2, 3, 4, 5]);
looped[4].next = looped[1]; // 5 points back to 2
console.log(hasCycle(looped[0])); // Output: true
const selfLoop = build([7]);
selfLoop[0].next = selfLoop[0]; // a single node pointing to itself
console.log(hasCycle(selfLoop[0])); // Output: true
console.log(hasCycle(null)); // Output: falseTrace for the looped list 1 → 2 → 3 → 4 → 5 → back to 2:
| Step | slow | fast |
|---|---|---|
| start | 1 | 1 |
| 1 | 2 | 3 |
| 2 | 3 | 5 |
| 3 | 4 | 3 |
| 4 | 5 | 5 → meet |
How it works
Why do they always meet if there is a cycle? Once both pointers are inside the loop, think of the gap from fast to slow going around the loop. Each step, slow moves 1 and fast moves 2, so fast closes the gap by exactly 1. A gap that shrinks by 1 each step must hit 0; it cannot jump over it. So they meet within one lap of the loop.
Why does it stop if there is no cycle? The fast pointer reaches the end in about n/2 steps, and the loop condition checks for null before every jump.
A simpler alternative is to store every visited node in a Set and report a cycle when you see a node twice. It is easy to explain but uses O(n) extra memory, so interviewers usually ask for Floyd's method next.
Complexity
| Approach | Time | Extra space |
|---|---|---|
| Hash set of visited nodes | O(n) | O(n) |
| Floyd's slow and fast pointers | O(n) | O(1) |
Common mistakes
- Checking only
fast !== nulland then callingfast.next.next, which crashes whenfast.nextis null. - Comparing node values (
slow.val === fast.val) instead of the nodes themselves. Different nodes can hold the same value. - Checking
slow === fastbefore moving them. They start at the same node, so the function would wrongly return true at once. - Forgetting the empty list and single-node cases.
What to say in the interview
"I would use Floyd's cycle detection. I keep a slow pointer moving one node at a time and a fast pointer moving two. If there is no cycle, fast reaches null and I return false. If there is a cycle, both pointers end up in the loop, and since fast gains one node per step on slow, they must meet, so I return true. It is O(n) time and O(1) space. A hash set of visited nodes also works but uses O(n) space."
Follow-up questions
- How do you find the node where the cycle begins?
- How do you find the length of the cycle?
- How can the same idea find a duplicate number in an array of n + 1 integers from 1 to n?
- How would you find the middle of a linked list in one pass?
Go deeper
Read these once the basics above feel easy.
Finding where the cycle starts: after slow and fast meet, move one pointer back to the head. Now move both pointers one step at a time. The node where they meet again is the start of the cycle.
function cycleStart(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
const a = { val: 1, next: null }, b = { val: 2, next: null }, c = { val: 3, next: null };
a.next = b; b.next = c; c.next = b; // 1 -> 2 -> 3 -> back to 2
console.log(cycleStart(a).val); // Output: 2Cycle length: once they meet, keep one pointer still and walk the other around the loop, counting steps until it comes back.
Why the reset trick works: let the distance from the head to the cycle start be L, the cycle length C, and the distance from the cycle start to the meeting point x. When they meet, slow has walked L + x and fast has walked twice that. The extra distance fast travelled is a whole number of laps, so L + x = kC for some k ≥ 1, which gives L = kC - x. A pointer starting at the head and a pointer starting at the meeting point, both moving one step at a time, therefore reach the cycle start after exactly L steps.