The obvious way to detect a cycle is to remember every node visited and check if you ever revisit one. The constant-space trick is to use two pointers moving at different speeds β if there is a loop, the faster one is guaranteed to lap the slower one from behind.
Hash Set of Visited Nodes
Time O(n)Space O(n)Walk the list, storing each nodeβs identity in a set. If you ever encounter a node already in the set, the next pointers have looped back on themselves.
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next
class Solution: def hasCycle(self, head: ListNode | None) -> bool: seen = set() node = head while node: if node in seen: return True seen.add(node) node = node.next return FalseCorrect and simple, but storing a reference to every visited node costs O(n) extra space β for a problem that is really just βdid we ever come back to somewhere we have been,β that is more memory than necessary.
Floyd's Cycle Detection (Slow / Fast Pointers)
OptimalTime O(n)Space O(1)Move a slow pointer one step at a time and a fast pointer two steps at a time. If there is no cycle, fast reaches the end first and the loop terminates. If there is a cycle, fast enters it first and, moving twice as fast as slow, closes the gap between them by exactly one node per iteration β so it is guaranteed to eventually land on the same node as slow.
class Solution: def hasCycle(self, head: ListNode | None) -> bool: slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow is fast: return True return FalseTracing head = [3,2,0,-4] with the tailβs next wired back to node 2 (index 1) β a cycle:
Start: slow and fast both at node 3. Note the tail (-4) loops back to node 2.
Why itβs correct: if a cycle exists, once fast enters it, the distance between fast and slow (measured along the cycle) shrinks by exactly one node every iteration, since fast gains one extra step per round β so it cannot skip over slow and must eventually meet it exactly. Complexity: fast traverses at most ~2n node visits before either exiting or meeting slow β O(n) time, and only two pointer variables are used β O(1) space β optimal, matching the follow-up requirement.