← Browse
Linked Lists

Cycle Detection

TimeO(n) SpaceO(1)

Picture two runners on a circular track. One runs twice as fast as the other. If the track is a loop, the fast runner will eventually lap the slow one and they’ll meet. If the track is a straight line, the fast runner just reaches the end. Meeting = cycle. No meeting = no cycle.

When to use it: Detecting cycles in linked lists, finding duplicate numbers in arrays (Floyd’s algorithm), or determining if a sequence of function applications enters a cycle.

Key insight: If a cycle exists, the fast pointer (moving 2 steps) will eventually catch the slow pointer (moving 1 step) inside the cycle. To find where the cycle starts, reset one pointer to the head and move both at speed 1 — they meet at the cycle entrance.

Common trap: Forgetting to find the cycle start after detecting it. Many problems need the entry point, not just a boolean “cycle exists.”

function hasCycle(head):
    slow = head
    fast = head

    while fast != null and fast.next != null:
        slow = slow.next            # move 1 step
        fast = fast.next.next       # move 2 steps

        if slow == fast:
            return true             # they met inside the cycle

    return false                    # fast reached the end, no cycle

function findCycleStart(head):
    slow = head
    fast = head

    # Phase 1: Detect cycle
    while fast != null and fast.next != null:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break

    if fast == null or fast.next == null:
        return null                 # no cycle

    # Phase 2: Find entry point
    slow = head                     # reset slow to head
    while slow != fast:
        slow = slow.next            # both move at speed 1
        fast = fast.next

    return slow                     # meeting point is cycle start
class ListNode:
    def __init__(self, val: int = 0, next: "ListNode | None" = None):
        self.val = val
        self.next = next


def has_cycle(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 False


def find_cycle_start(head: ListNode | None) -> ListNode | None:
    slow = fast = head

    # Phase 1: Detect cycle
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow is fast:
            break
    else:
        return None  # no cycle

    # Phase 2: Find entry point
    slow = head
    while slow is not fast:
        slow = slow.next
        fast = fast.next

    return slow


def find_duplicate(nums: list[int]) -> int:
    """Find the duplicate in [1..n] array with n+1 elements (Floyd's)."""
    slow = fast = nums[0]

    # Phase 1: Find meeting point
    while True:
        slow = nums[slow]
        fast = nums[nums[fast]]
        if slow == fast:
            break

    # Phase 2: Find entrance
    slow = nums[0]
    while slow != fast:
        slow = nums[slow]
        fast = nums[fast]

    return slow

Problem: Given an array of n+1 integers where each integer is between 1 and n inclusive, find the one duplicate number without modifying the array and using only O(1) extra space.

How would you approach this?

Answer: This is Floyd's cycle detection applied to an array. Treat each value as a pointer to the next index: next = nums[current]. A duplicate means two indices point to the same place, creating a cycle. Find the cycle entrance using the two-phase algorithm.


Problem: Given the head of a linked list, return the node where the cycle begins. If there is no cycle, return null.

How would you approach this?

Answer: This is Floyd's tortoise and hare. Phase 1: slow (1 step) and fast (2 steps) detect the cycle by meeting. Phase 2: reset slow to head, move both at speed 1 — they meet at the cycle entrance.

Time: O(n) because the slow pointer traverses at most the entire list, and the fast pointer makes at most two full traversals. Once inside the cycle, the fast pointer catches the slow pointer within one full cycle length.

Space: O(1) because only two pointer variables are used. This is the key advantage over using a hash set (which would be O(n) space).