Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 10 – Linked list patterns

Fast and slow pointers: middle and cycles

Find the middle of a linked list in one pass, detect a loop and the node where it starts, and find a repeated number in a read-only array.

  • Intermediate
  • 35 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free intro, Pro: 8 of 35 minutes free
Free: Two problems, one chain · The brute force, and why it fails at these sizes · What to notice. With a pass: the other 12 sections and 3 interview answers, about 27 minutes.

What you will learn

  • Find the middle node of a list in one pass, and choose the first or the second middle on purpose
  • Detect a loop and the node where it starts with Floyd's method, in O(n) time and O(1) memory
  • Prove why the second walk of Floyd's method stops exactly at the loop's first node
  • Apply cycle detection to an array read as a chain, to find a repeated number without changing the array
  • Compare Floyd's and Brent's methods by the number of steps each one needs

Before you start

What you’ll learn · Before you start

What you will learn

  • Find the middle node of a list in one pass, and choose the first or the second middle on purpose
  • Detect a loop and the node where it starts with Floyd's method, in O(n) time and O(1) memory
  • Prove why the second walk of Floyd's method stops exactly at the loop's first node
  • Apply cycle detection to an array read as a chain, to find a repeated number without changing the array
  • Compare Floyd's and Brent's methods by the number of steps each one needs

Before you start

On this page

Fast and slow pointers are two references that walk the same chain at different speeds: the slow one moves one node a step, the fast one two. If the chain ends, the fast pointer gets there when the slow one is in the middle. If the chain loops, the fast pointer gains one node a step and catches the slow one inside the loop, and a second walk from the start stops where the loop begins: Floyd’s cycle method, with O(n) time and O(1) memory. The chain need not be a linked list. A list whose values are positions in that list is one too, which is how the method finds a repeated number among n + 1 values from 1 to n without changing them, the problem known as find the duplicate number.

Two problems, one chain

Problem A: a redirect loop. A link shortener resolves each short link through a chain of redirects: every node holds an address and the next node, or None at the final address. A misconfigured chain loops back on itself. Given the first node, return None if the chain ends, or the first node of its loop, with O(1) extra memory and without changing any node. A chain has up to 10⁶ nodes. So a → b → c → d → e → c gives c, and a → b → c gives None.

Problem B: a ticket printed twice. A machine printed n + 1 tickets, each numbered from 1 to n, so some number was printed more than once, and exactly one number was. Return it with O(1) extra memory, without changing the list; 1 ≤ n ≤ 10⁵. So [4, 2, 5, 1, 3, 5] gives 5, [3, 3, 3, 3] gives 3, and [2, 6, 4, 1, 3, 6, 5] gives 6.

The brute force, and why it fails at these sizes

Without memory for visited nodes, the direct method for A walks the chain and, at the k-th node, walks k nodes from the start again to see whether it came before: about n²/2 steps, 5 × 10¹¹ for a million nodes. For B, count each number from 1 to n: about 10¹⁰ comparisons at the limit. A set of visited items, or a sorted copy, is fast but needs O(n) memory, and sorting the list itself changes it.

Both brute forces, with the work counted

brute/brute_force.py

"""Brute force for both problems, with the work counted.

A: the first node of a redirect loop, using O(1) memory: walk the chain, and at each step walk again from the start
to check whether the node just reached was seen before. B: the repeated ticket, by counting every possible number.
"""
from chains import chain


def loop_start_brute(first):
    """Return (the first node seen twice, or None, steps). Step k walks k nodes from the start again."""
    steps, node, k = 0, first, 0
    while node is not None:
        probe = first
        for _ in range(k):  # is node among the k nodes before it?
            steps += 1
            if probe is node:
                return node, steps
            probe = probe.next
        node, k = node.next, k + 1
    return None, steps


def repeated_ticket_brute(tickets):
    """Return (the number that appears twice or more, comparisons), counting each number from 1 to n."""
    comparisons = 0
    for v in range(1, len(tickets)):
        count = 0
        for t in tickets:
            comparisons += 1
            count += t == v
        if count > 1:
            return v, comparisons
    return None, comparisons


start, steps = loop_start_brute(chain("abcde", loop_to=2))
print(f"A: a -> b -> c -> d -> e -> back to c: the loop starts at {start.address} ({steps} steps)")
start, steps = loop_start_brute(chain("abc"))
print(f"A: a -> b -> c with no loop: {start} ({steps} steps)")
for n in (500, 1_000):  # a tail of n/2 nodes, then a loop of n/2 nodes
    kept = chain(range(n), loop_to=n // 2)
    _, steps = loop_start_brute(kept)
    print(f"A: {n:>5,} nodes: {steps:>9,} steps")

for tickets in ([4, 2, 5, 1, 3, 5], [3, 3, 3, 3], [2, 6, 4, 1, 3, 6, 5]):
    print(f"B: {tickets} -> {repeated_ticket_brute(tickets)[0]}")
for n in (1_000, 2_000):  # the repeated number is n, the last one the brute force counts
    _, comparisons = repeated_ticket_brute(list(range(1, n + 1)) + [n])
    print(f"B: n = {n:>5,}: {comparisons:>9,} comparisons")
n = 100_000
print(f"B: n = {n:,}: about {n * (n + 1):,} comparisons, {n * (n + 1) / 1e7 / 60:.0f} minutes at 10^7 a second")

Output

A: a -> b -> c -> d -> e -> back to c: the loop starts at c (13 steps)
A: a -> b -> c with no loop: None (3 steps)
A:   500 nodes:   125,001 steps
A: 1,000 nodes:   500,001 steps
B: [4, 2, 5, 1, 3, 5] -> 5
B: [3, 3, 3, 3] -> 3
B: [2, 6, 4, 1, 3, 6, 5] -> 6
B: n = 1,000: 1,001,000 comparisons
B: n = 2,000: 4,002,000 comparisons
B: n = 100,000: about 10,000,100,000 comparisons, 17 minutes at 10^7 a second

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute_force.py

brute/chains.py

"""Helpers for chains of redirects: nodes with an address and the next node, which may loop back."""


class Node:
    """One redirect: an address and the next node (None after the last address)."""

    def __init__(self, address, next=None):
        self.address = address
        self.next = next


def chain(addresses, loop_to=None):
    """Build a chain of nodes in order; with loop_to = i, the last node points back at node i. Returns the first node."""
    nodes = [Node(a) for a in addresses]
    for a, b in zip(nodes, nodes[1:]):
        a.next = b
    if loop_to is not None and nodes:
        nodes[-1].next = nodes[loop_to]
    return nodes[0] if nodes else None

Doubling n quadruples both counts, and B alone would need about 17 minutes of CPython at its limit.

What to notice

In A, a walk that never ends is going round a loop, and two walkers at different speeds inside a loop must meet. In B, read the list as a chain: from index i, go to index tickets[i]. No ticket is 0, so the walk from index 0 never returns to it and must end in a loop, and two indices point at the loop’s first index because they hold the same number. B is A in disguise, which is the signal for fast and slow pointers.

The tickets 2, 6, 4, 1, 3, 6, 5 as a chain from index 0: a tail of five indices, then a loop of 6 and 5 that index 1 enters.tickets [2, 6, 4, 1, 3, 6, 5] as a chainindex 0holds 2index 2holds 4index 4holds 3index 3holds 1index 1holds 6index 6holds 5index 5holds 6the loop startsat index 6:two arrows intail 0, 2, 4, 3, 1, then the loop 6, 5

A list of tickets read as a chain of indices

Text description of the diagram

The ticket list [2, 6, 4, 1, 3, 6, 5] is drawn as a chain: each index points at the index named by the ticket it holds.

Starting at index 0, which holds 2, the walk goes to index 2, which holds 4; then to index 4, which holds 3; to index 3, which holds 1; and to index 1, which holds 6. These five indices form the tail.

Index 1 points at index 6, which holds 5, and index 6 points at index 5, which holds 6 again, so index 5 points back at index 6. Indices 6 and 5 form the loop, and the walk goes round them for ever.

Index 6, where the loop starts, has a thick border. Two arrows point at it, one from index 1 and one from index 5, because both of those indices hold the ticket 6: the number that was issued twice.

The middle of a chain in one pass Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Floyd’s method, step by step
    • Why the pointers must meet inside the loop
    • The proof that phase 2 stops at the entry, with distances
    • A dry run on the ticket list
  • Why the loop’s first index is the repeated ticket
  • The code in Python, JavaScript, Java, C++ and Go
  • Brent’s method, and the function calls it saves, measured
  • Edge cases and slips, caught by a stress test
  • Variants, with answers
  • Exercise: where a walk through an array starts to loop
    • Find where a walk through an array starts to loop
  • A practice set with answers
  • Follow-up questions, answered
  • Check yourself
  • Interview questions
    • If a linked list has a loop, why must a pointer that moves two nodes a step meet one that moves one node a step?
    • An array holds n + 1 integers, each between 1 and n, and exactly one value repeats, perhaps more than twice. The array is read-only and you may use O(1) extra memory. Find the value, and compare your method with a binary search on counts.
    • A link shortener stores redirects as database rows that point at the next row, and resolving a link costs one read per hop. A loop in the data made requests hang. How do you detect loops, how many reads does each method cost, and what do you change so that a request can never hang this way again?
    • In Floyd's method the fast pointer moves two nodes a step. Would a fast pointer of three nodes still detect a loop? Would its second phase still find where the loop starts? Prove each answer or give a counterexample.
  • Key takeaways

6 runnable examples (C++, Go, Java, JavaScript, Python, chains.mjs, chains.py, floyd.py) · 1 exercise with sample tests · 3 interview answers · about 26 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.