Linked List Cycle asks a simple yes/no question — but the elegant answer, Floyd's tortoise and hare, is a classic interview favorite. It detects a loop using two pointers and zero extra memory.
Problem. Given the head of a linked list, return True if the list contains a cycle (some
node's next eventually points back to an earlier node), and False otherwise.
Example: the list 1 → 2 → 3 → 4 → 5, where node 5's next points back to node 3. That loop means
the answer is True.
The slow way first
The obvious idea: walk the list and remember every node you have visited in a hash set. The moment you reach a node already in the set, you have found a cycle. That works and is O(n) time — but it costs O(n) extra space for the set.
The question to ask: can I detect the loop without storing every node? Yes — by racing two pointers at different speeds.
The idea: two pointers, two speeds
Send two pointers down the list from the head. slow advances one node per step; fast advances two. Think of two runners on a track:
- If the list ends (no cycle), the fast runner reaches the finish line (
None) and we returnFalse. - If the list loops, neither runner can escape. The fast runner gains exactly one node on the slow runner every step, so it is guaranteed to lap it — and they land on the same node.
The key insight: meeting can only happen inside a cycle. A meeting is a proof of a loop.
Walk through it
Step through the animation. Both pointers start on node 1. Each step, slow (blue) takes one hop and fast (red) takes two. They pull apart — until fast wraps around the back edge 5 → 3. On the next jump fast lands on node 4, exactly where slow is. slow == fast, so we return True.
Pseudocode
slow = head
fast = head
while fast is not None and fast.next is not None:
slow = slow.next # one hop
fast = fast.next.next # two hops
if slow == fast: # same node?
return True # they met -> cycle
return False # fast ran off the end -> no cycleThe Python solution
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return Falseslow = fast = headstarts both pointers at the same place.- The loop guard
while fast and fast.nextis what catches a non-cyclic list: iffast(or the node after it) isNone, the list ended and we fall through toreturn False. slow = slow.nextmoves one node;fast = fast.next.nextmoves two.- Line 6 compares the two nodes (identity), not their values. Equal nodes mean the pointers collided inside a loop.
- No set, no recursion — just two pointers, so the extra space is O(1).
Complexity
| Case | Time | Notes |
|---|---|---|
| Hash-set (store visited) | O(n) (moderate) | O(n) extra space |
| Floyd two-pointer (this) | O(n) (moderate) | O(1) extra space |
O(1) (fast)Both approaches are O(n) time, but Floyd's needs only two pointers — constant extra space. The fast pointer travels at most about twice around the loop before meeting the slow one, so the running time stays linear.
When this pattern shows up
Two pointers at different speeds (the "tortoise and hare") is the go-to move for linked-list cycle questions: detect a cycle, find where it starts, or find the middle of a list. Whenever you need to reason about a loop or a midpoint in O(1) space, reach for fast and slow pointers.
Mind the loop guard. You must check both fast and fast.next before calling fast.next.next — on
a list with an even length, skipping that check dereferences None and crashes.
Practice
The list is 1 -> 2 -> 3 -> 4 -> 5 with 5 looping back to 3. After two iterations slow is on 3 and fast is on 5. On the next iteration, where do slow and fast land?
1. Why does Floyd's algorithm use O(1) space while the hash-set approach uses O(n)?
2. If the list has NO cycle, how does the algorithm terminate?
3. Why are slow and fast guaranteed to meet when a cycle exists?
4. Why must we check both fast and fast.next in the loop condition?