Pattern 8 of 24 · Linked lists · 25 min

Fast and Slow Pointers

Move one pointer twice as fast as the other to detect cycles, find the middle, or find where a loop starts, in O(1) space.

Spot it:linked list cyclemiddle of a lista sequence that may loopO(1) extra spaceJavaScript you'll use: Linked list and tree nodes
Say it in 30 seconds
  • O(n)Time: slow is caught within its first lap
  • O(1)Extra space, against O(n) for a visited Set
  • 1 and 2Steps per move for slow and fast
Show what to say

“When a linked list may have a cycle, or I need its middle, or a rule-defined sequence may repeat, and O(1) space is required, I run two pointers: slow moves one step, fast two. Fast laps slow inside a loop so they meet, and a second walk from the head finds where the loop starts.”

Trade-offs

  • Floyd vs a visited Set: the Set is simpler and also O(n) time but costs O(n) memory; Floyd is O(1) space and answers the follow-up.
  • Speed 2 vs 3: both meet, but only speed 2 makes the second walk land on the entry; with 3 it can miss.
  • Second middle vs first middle: 'fast && fast.next' stops on the second on even lengths; 'fast.next && fast.next.next' gives the first, for cutting.

Bring it up before they ask

Check fast before fast.next in the loop condition (null.next throws), compare nodes rather than values, and remember the meeting point is inside the loop, not necessarily its entry.

0 of 0 sections ticked
30-second answer

Two runners on one list: slow takes one step, fast takes two. When fast runs out, slow is at the middle. In a loop, fast laps slow and they meet. All in O(1) extra space.

When to reach for it

Spot it in the problem0 of 3

Tap the signals you can see in the problem.

Rememberlinked listcyclemiddlesequence defined by a ruleO(1) extra space

Pick your variant
  • Find the middle

    Pointersslow +1, fast +2

    middle of the list
  • Cycle and its entry

    Pointers+1 and +2 until they meet; then both +1

    linked list cycle I and II
  • Implicit sequence

    Pointersx → f(x) instead of node.next

    happy numberfind the duplicate

The core idea: a faster runner laps a slower one

Two runners, one track. On a straight road the fast one finishes having run twice as far, so the slow one is half way: the middle.

Read more · 105 words

If the road ends in a loop, both circle it, and the fast one gains exactly one node per step. A gap that shrinks by one can't jump over zero, so they meet. Watch both phases, or build your own list with Edit input:

Why the second walk finds the entry. Let F be the steps from the head to the entry, and a how far past the entry they meet. Fast walked twice what slow did, so slow's steps are whole laps. Slow walked F + a, so F + a is whole laps, and F more steps from the meeting point land on the entry.

Floyd's cycle detection, then walk to the cycle's entry

list
3851947▲ slow▲ fast
steps
0

Both pointers start on the head. The last node (7) links back to 5, but the code can't see that: it only follows next pointers.

function cycleStart(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      let entry = head;
      while (entry !== slow) {
        entry = entry.next;
        slow = slow.next;
      }
      return entry;
    }
  }
  return null;
}
All steps as text
  1. Both pointers start on the head. The last node (7) links back to 5, but the code can't see that: it only follows next pointers.
  2. slow moves one node to 8; fast moves two, to 5.
  3. slow moves one node to 5; fast moves two, to 9.
  4. slow moves one node to 1; fast moves two, to 7.
  5. slow moves one node to 9; fast moves two, to 1.
  6. slow moves to 4 and fast jumps two nodes to 4: the same node.
  7. They meet 3 steps past the entry (a = 3). slow walked F + a = 2 + 3 = 5 steps and fast walked 10; the extra 5 is a whole number of laps of the loop (C = 5). So F = (whole laps) - a.
  8. Put entry on the head and leave slow on the meeting point. Both are now exactly F = 2 steps (plus whole laps) from where the loop begins.
  9. Both move one step: entry to 8, slow to 7. Still apart.
  10. Both move one step and land on 5 together, after 2 steps = F.
  11. The cycle begins at 5. Two pointers and two short walks: O(n) time, O(1) memory.

Play with it

Set the tail and the loop, press Run, and watch the hare lap the tortoise.

Read more · 62 words
  1. Find the hidden list

    What is next? node.next, or i → nums[i].

  2. Pick the loop condition

    while (fast && fast.next) keeps fast.next.next safe.

  3. Move, then compare

    Both start on the head. Compare nodes (slow === fast), never values.

  4. Decide what the meeting means

    fast hit null: no cycle. A meeting: a cycle.

  5. Check the tiny cases

    Empty list, one node.

Interactive

Tortoise and hare

The hare laps the tortoise, then both walk to where the loop begins.

0head123entry45678slowfast
Steps: 0Memory: 2 pointers (a visited set: up to 9 nodes)

Both start on the head. Fast takes 2 hops for each step slow takes. Press Run or Step.

Templates

Template 1: find the middle

The two loop conditions differ only on even lengths:

Read more · 46 words
Loop condition1 → 2 → 3 → 4 → 51 → 2 → 3 → 4 → 5 → 6Use it for
fast && fast.next34 (second middle)the middle node
fast.next && fast.next.next33 (first middle)cutting the list

Middle of the Linked ListEasy is this template; Palindrome Linked ListEasy starts with it.

Find the middle: fast moves two nodes per step

list
1234567▲ slow▲ fast
steps
0
slow at
1
fast at
1

Both pointers start on the head. The list has 7 nodes, but the code never needs to know that.

function middleNode(head) {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}
All steps as text
  1. Both pointers start on the head. The list has 7 nodes, but the code never needs to know that.
  2. slow takes one step, to 2. After 1 step it has covered 1 node.
  3. fast takes two steps, to 3: it has covered 2 nodes, twice as many as slow.
  4. slow takes one step, to 3. After 2 steps it has covered 2 nodes.
  5. fast takes two steps, to 5: it has covered 4 nodes, twice as many as slow.
  6. slow takes one step, to 4. After 3 steps it has covered 3 nodes.
  7. fast takes two steps, to 7: it has covered 6 nodes, twice as many as slow.
  8. fast is on the last node (fast.next is null), so it can't take two more steps. Stop.
  9. fast covered the whole list while slow covered half of it, so slow is on the middle node, 4.
// Template 1: find the middle. fast moves two nodes for every one that slow moves,
// so slow is half way along when fast runs out of list.
export function middleNode(head) {
  let slow = head;
  let fast = head;
  while (fast && fast.next) { // even length: slow ends on the second middle
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}

// Variant for splitting a non-empty list: the last node of the first half (the first middle on even lengths).
export function endOfFirstHalf(head) {
  let slow = head;
  let fast = head;
  while (fast.next && fast.next.next) {
    slow = slow.next;
    fast = fast.next.next;
  }
  return slow;
}

Template 2: detect a cycle and find its entry

Phase 1: move at speeds 1 and 2 until they meet or fast runs out.

Read more · 25 words

Phase 2: restart one at the head and step both by one until they meet at the entry.

Linked List CycleEasy needs phase 1; Linked List Cycle IIMedium needs both.

// Template 2: Floyd's cycle detection. Inside a loop, fast gains exactly one node per step on slow,
// so it can't jump over it: they meet. Without a loop, fast falls off the end.
export function hasCycle(head) {
  let slow = head;
  let fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) return true; // the same node, not just an equal value
  }
  return false;
}

// Phase 2: the head and the meeting point are equally far from the cycle's entry (up to whole laps),
// so two pointers moving one step at a time from those two places meet exactly at the entry.
export function cycleStart(head) {
  let slow = head;
  let fast = head;
  while (fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      let entry = head;
      while (entry !== slow) {
        entry = entry.next;
        slow = slow.next;
      }
      return entry;
    }
  }
  return null;
}

Template 3: cycles in implicit sequences

Floyd's algorithm only needs "the next one".

Read more · 62 words

Any rule f over a finite set makes a sequence that must repeat: a tail into a loop. Replace node.next with f(x) and the same two phases work.

Cycles without a list: x → f(x)

f(x) for x = 0, 1, 2, … (8)
4
0
7
2
6
3
2
5
▲ slow
▲ fast
the sequence from x0 = 0 (10)
0
4
6
2
7
5
3
2
7
5
slow
0
fast
0

There are no nodes here: the "next" of x is f(x), read from the table. Following it from 0 gives 0 → 4 → 6 → 2 → 7 → 5 → 3 → 2 → …, which must repeat because there are only 8 possible values.

function floyd(f, x0) {
  let slow = x0, fast = x0;
  do {
    slow = f(slow);
    fast = f(f(fast));
  } while (slow !== fast);
  let entry = x0, tail = 0;
  while (entry !== slow) {
    entry = f(entry); slow = f(slow); tail++;
  }
  let length = 1;
  for (let x = f(entry); x !== entry; x = f(x)) length++;
  return { start: entry, tail, length };
}
All steps as text
  1. There are no nodes here: the "next" of x is f(x), read from the table. Following it from 0 gives 0 → 4 → 6 → 2 → 7 → 5 → 3 → 2 → …, which must repeat because there are only 8 possible values.
  2. slow = f(slow) = 4; fast = f(f(fast)) = 6. Different values, keep going.
  3. slow = f(slow) = 6; fast = f(f(fast)) = 7. Different values, keep going.
  4. slow = f(slow) = 2; fast = f(f(fast)) = 3. Different values, keep going.
  5. slow = f(slow) = 7 and fast = f(f(fast)) = 7: they meet, so the sequence has entered its cycle.
  6. Same argument as for linked lists: x0 and the meeting point 7 are equally far from the start of the cycle. Restart entry at 0 and move both one step at a time.
  7. entry = 4, slow = 5. Not equal yet.
  8. entry = 6, slow = 3. Not equal yet.
  9. entry = 2 and slow = 2: they agree, so the cycle starts at 2, 3 steps after x0.
  10. Walk around the cycle to measure it: 2 → 7 so far, length 2.
  11. Walk around the cycle to measure it: 2 → 7 → 5 so far, length 3.
  12. Walk around the cycle to measure it: 2 → 7 → 5 → 3 so far, length 4.
  13. The sequence runs 3 steps into a cycle of length 4 that starts at 2. Happy Number and Find the Duplicate are exactly this, with f = digit-square sum and f(i) = nums[i].
// Template 3: a cycle in an implicit sequence x0, f(x0), f(f(x0)), … Nothing is stored: f plays the role of node.next.
// Returns where the repeating part starts, how many steps lead into it (tail) and how long it is.
export function floyd(f, x0) {
  let slow = x0;
  let fast = x0;
  do {                     // phase 1: meet somewhere inside the cycle
    slow = f(slow);
    fast = f(f(fast));
  } while (slow !== fast);
  let entry = x0;          // phase 2: equal distances lead to the start of the cycle
  let tail = 0;
  while (entry !== slow) {
    entry = f(entry);
    slow = f(slow);
    tail++;
  }
  let length = 1;          // phase 3: walk once around the cycle
  for (let x = f(entry); x !== entry; x = f(x)) length++;
  return { start: entry, tail, length };
}

Try it

try-it.js
// Edit the inputs and press Run. fromArray(values, pos) links the tail back to index pos (-1: no cycle).
console.log(toArray(middleNode(fromArray([1, 2, 3, 4, 5]))));     // [3, 4, 5]
console.log(toArray(middleNode(fromArray([1, 2, 3, 4, 5, 6]))));  // [4, 5, 6]

const looped = fromArray([3, 8, 5, 1, 9, 4, 7], 2);
console.log(hasCycle(looped), cycleStart(looped).val);            // true 5
console.log(hasCycle(fromArray([1, 2, 3])));                      // false

const digitSquares = (n) => String(n).split('').reduce((sum, d) => sum + d * d, 0);
console.log(floyd(digitSquares, 19)); // { start: 1, tail: 4, length: 1 }: 19 is happy
console.log(floyd(digitSquares, 2));  // { start: 4, tail: 1, length: 8 }: 2 is not

Complexity and JavaScript you'll use

Complexity

Read more · 71 words
VariantTimeExtra space
Find the middleO(n)O(1)
Detect a cycle and find its entryO(n)O(1)
Implicit sequenceO(tail + cycle length)O(1)
Visited Set, for comparisonO(n)O(n)

JavaScript you'll use

JavaScript refresher: Linked list and tree nodes

=== on objects means the same node. Comparing .val does not.

while (fast && fast.next) {       // check fast first: null.next throws
  slow = slow.next;
  fast = fast.next.next;
  if (slow === fast) break;       // === on objects compares identity: the same node
}
const seen = new Set();           // the O(n)-space alternative: seen.has(node)

Interview questions

Why does fast move two steps? Would three work?

Three also meets, but only speed 2 makes phase 2 land on the entry. With 3 the second walk can miss.

How do you find the length of the cycle?

Hold one pointer, step the other around, count.

Visited Set or Floyd: which do you write first?

The Set: simpler, O(n) memory. Floyd answers the follow-up, "O(1) space?".

How do you find where two linked lists intersect?

Walk a pointer from each head; at the end, switch each to the other head. They meet there.

Can you check a linked list is a palindrome in O(1) space?

Find the middle, reverse the second half (list reversal), compare.

Check yourself

Question 1 of 3

Score 2 out of 3 or better to mark this page complete.

1. Inside a loop, slow moves one node per step and fast moves two. Why can't fast jump over slow without landing on it?

Practice problems

Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.

Open these in the problem list →

Next: Linked lists are also rewired in place, by reversing next pointers.