Detect a Cycle in a Linked List: Floyd’s Algorithm and Cycle Entry Point
Detecting a cycle in a linked list (LeetCode #141 and #142) is a foundational coding interview question. The base problem is “is there a cycle?”; the more interesting follow-up is “if so, find where the cycle begins.” Both are standard at FAANG, AI labs, and the broader tech interview circuit. The expected answer is Floyd’s tortoise-and-hare algorithm — fast and slow pointers — which solves both versions in O(n) time and O(1) space. This guide covers the mechanics, the math behind the cycle-entry trick, and the variations interviewers ask as follow-ups.
Problem Statement
Part 1: Given the head of a linked list, return True if there is a cycle (some node’s next points back to a previously-visited node), otherwise False.
Part 2: If a cycle exists, return the node where the cycle begins.
Approach 1: Hash Set (O(n) Space)
Walk the list. Track visited nodes in a hash set. If you encounter a node already in the set, that’s the cycle entry. If you reach None, no cycle.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def has_cycle_set(head: ListNode) -> bool:
"""O(n) time, O(n) space."""
seen = set()
while head:
if head in seen:
return True
seen.add(head)
head = head.next
return False
def cycle_entry_set(head: ListNode) -> ListNode:
"""O(n) time, O(n) space."""
seen = set()
while head:
if head in seen:
return head
seen.add(head)
head = head.next
return None
Complexity: O(n) time, O(n) space. Acceptable but suboptimal — interviewers will push for O(1) space.
Approach 2: Floyd’s Tortoise and Hare (O(1) Space)
Two pointers: one moves one step at a time (slow), the other moves two steps at a time (fast). If there’s a cycle, the fast pointer eventually laps the slow pointer and they meet inside the cycle. If there’s no cycle, the fast pointer reaches the end.
def has_cycle(head: ListNode) -> bool:
"""O(n) time, O(1) space — Floyd's tortoise and hare."""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Complexity: O(n) time, O(1) space.
This is the answer the interviewer expects.
Finding the Cycle Entry Point
Once slow and fast meet inside the cycle, restart one pointer at the head and advance both at the same speed. They meet at the cycle entry.
def cycle_entry(head: ListNode) -> ListNode:
"""Return the node where the cycle begins, or None if no cycle."""
if not head or not head.next:
return None
# Phase 1: detect cycle
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break
else:
return None # no cycle
if slow != fast:
return None # no cycle (fast hit end)
# Phase 2: find entry
pointer = head
while pointer != slow:
pointer = pointer.next
slow = slow.next
return pointer
The Math Behind Phase 2
Why does restarting from the head and advancing at equal speed land exactly at the cycle entry? Let:
L= distance from head to cycle entryC= length of the cyclex= distance from cycle entry to the meeting point (going around the cycle)
When slow and fast meet, slow has traveled L + x, fast has traveled L + x + kC for some integer k (fast made k extra laps). Fast moves twice as fast, so:
2(L + x) = L + x + kC ⇒ L + x = kC ⇒ L = kC - x
This means the distance from the head to the cycle entry equals (k laps minus x). If we restart one pointer at the head and advance both pointers one step at a time, after L steps the head pointer is at the entry, and the slow pointer has moved L additional steps from the meeting point — which lands it at the entry too (because x + L = kC, an integer number of laps). They meet at the cycle entry.
Strong candidates can sketch this argument verbally; full derivation isn’t usually expected in an interview but signals depth.
Common Variations
Cycle length
Once slow and fast meet, freeze slow and advance fast around the cycle. Count steps until fast meets slow again. That count is the cycle length.
def cycle_length(head: ListNode) -> int:
"""Return cycle length, or 0 if no cycle."""
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break
else:
return 0
if slow != fast:
return 0
length = 1
fast = fast.next
while fast != slow:
fast = fast.next
length += 1
return length
Find duplicate number using cycle detection
(LeetCode #287) Given an array nums of n+1 integers in range [1, n], find the duplicate. Treat the array as a linked list where index i “points to” nums[i]. The duplicate causes a cycle, and the cycle entry is the duplicate value. Floyd’s algorithm finds it in O(n) time, O(1) space without modifying the array.
def find_duplicate(nums: list[int]) -> int:
"""Find duplicate in [1, n] in O(n) time, O(1) space."""
slow = fast = nums[0]
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
pointer = nums[0]
while pointer != slow:
pointer = nums[pointer]
slow = nums[slow]
return pointer
Happy number
(LeetCode #202) Compute the sum of squares of digits repeatedly. The sequence either reaches 1 (happy) or enters a cycle (not happy). Floyd’s algorithm detects the cycle in constant space.
Detecting cycles in directed graphs
Different problem: graph cycle detection uses DFS with three colors (white/gray/black) to detect back-edges. Floyd’s algorithm doesn’t directly apply because graph nodes have multiple successors.
Common Mistakes
- Off-by-one in fast pointer advancement. The check should be
while fast and fast.next, notwhile fast.next(which crashes iffastis None). Both must be non-None forfast.next.nextto be valid. - Initializing slow and fast to
head.next. Some implementations start them offset; this works for cycle detection but breaks the cycle-entry math. Useslow = fast = head. - Forgetting the edge case of a single-node cycle. A node whose
nextpoints to itself: slow and fast meet immediately at the node. The cycle-entry phase should still work (head and slow are equal at start, and Phase 2 returns immediately). - Using
id()instead of object identity. Python’s default==on objects checks identity, which is correct here. Don’t override__eq__on ListNode; if you do, comparison may compare values rather than nodes, breaking the algorithm. - Modifying the list during traversal. Some bad solutions break the cycle by setting
node.next = None, which destroys the input. Don’t mutate the list unless the problem allows it.
Frequently Asked Questions
What’s the expected interview answer?
Floyd’s tortoise and hare. Detect cycle in O(n) time and O(1) space. If asked for the cycle entry, do the second phase (restart one pointer at head, advance both equally until they meet). Walk through the math briefly when asked why phase 2 works; full derivation is impressive but not required.
When would I use the hash-set approach instead?
Rarely in production-style code, but reasonable as a first answer if you’re warming up. The hash-set approach is simpler to write and equally fast in time complexity. Mention it, then optimize to Floyd’s. Some interviewers accept either; most push toward Floyd’s for the O(1) space win.
How do I handle the empty-list case?
Return False (no cycle). The loop body never executes if head is None, so the standard implementation handles it correctly. Single-node lists with no cycle: fast.next is None, loop exits, return False. Test these cases explicitly.
Why does fast move two steps and slow move one?
Any pair where fast moves strictly faster than slow guarantees they meet inside a cycle (if one exists). Two-step is the simplest choice that guarantees fast doesn’t skip past slow on a cycle of any length ≥ 1. Three-step would also work for detection but breaks the elegant cycle-entry math. Two-step is canonical.
What’s the alternative if I can’t use Floyd’s algorithm?
If forbidden, the hash-set approach is the next-best at O(n) time and space. Some textbooks describe a “Brent’s algorithm” variant that uses fewer pointer comparisons in practice, but it’s rarely asked. For interview purposes, Floyd’s is the answer; alternatives matter only if the interviewer specifically rules it out.
See also: Check if a Linked List is a Palindrome • Reverse a Doubly Linked List • Serialize and Deserialize a Binary Tree
💡Strategies for Solving This Problem
Classic Problem, Elegant Solution
Got this at Amazon. It's a standard problem but the solution (Floyd's Cycle Detection) is so clever that when you see it, it clicks forever.
The Insight
Imagine two runners on a circular track. If one runs twice as fast, they'll eventually lap the slower runner. Same idea with pointers in a linked list.
Floyd's Algorithm (Tortoise and Hare)
- Use two pointers: slow (moves 1 step) and fast (moves 2 steps)
- If there's a cycle, fast will eventually catch up to slow
- If there's no cycle, fast reaches the end (null)
This is O(n) time and O(1) space. You can't do better.
Alternative: Hash Set (Not Optimal)
Store visited nodes in a Set. If you see a node twice, there's a cycle. Works but uses O(n) space. My Amazon interviewer said "Can you do it without extra space?" - that's your cue for Floyd's.
Follow-Up Questions They'll Ask
- "Where does the cycle begin?" - This is the hard part. After detecting a cycle, there's a mathematical proof about where the entry point is.
- "What's the length of the cycle?" - Once pointers meet, count how many steps until they meet again.
I didn't know these follow-ups in my first Amazon interview. Study them.