MediumStale Closures🐛 Debug Challenge

setInterval always reads initial count

Buggy Code — Can you spot the issue?

let count = 0;
const log = [];

// Simulates useEffect(() => { setInterval... }, []) — missing count in deps
const savedCount = count; // closure captures 0 forever
const interval = setInterval(() => {
  log.push(savedCount); // always 0 — stale!
}, 0);

count = 1;
count = 2;
count = 3;

clearInterval(interval);
setTimeout(() => console.log(log[0]), 10);

Fixed Code

let count = 0;
const log = [];

// Ref pattern — always reads latest value
const countRef = { current: 0 };
const interval = setInterval(() => {
  log.push(countRef.current); // reads latest
}, 0);

count = 1; countRef.current = 1;
count = 2; countRef.current = 2;
count = 3; countRef.current = 3;

clearInterval(interval);
setTimeout(() => console.log(log[0]), 10);

Bug Explained

Bug: savedCount captures count's value (0) at creation time. Subsequent assignments to count don't affect the closed-over value.

Explanation: countRef.current is a property of a stable object — reading it at tick time always gives the latest value, not the value captured at creation.

Key Insight: Use useRef to store values you want setInterval/setTimeout to read as latest. The ref pattern: ref.current = value in useEffect, read ref.current inside the interval callback.

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab