MediumStale Closures🐛 Debug Challenge

Event handler reads stale count from first render

Buggy Code — Can you spot the issue?

let count = 0;
const clicks = [];

// Simulates onClick handler that closes over count
// Bug: handler is created once, closes over count=0
function createHandler() {
  const staleCount = count; // captures current value
  return function onClick() {
    clicks.push(staleCount + 1); // always 0 + 1 = 1
  };
}

const handler = createHandler(); // created once

// User clicks 3 times
handler();
handler();
handler();

console.log(clicks.join(','));

Fixed Code

let count = 0;
const clicks = [];

// Functional update pattern — doesn't need to close over count
function createHandler(getCount, setCount) {
  return function onClick() {
    setCount(prev => {
      const next = prev + 1;
      clicks.push(next);
      return next;
    });
  };
}

const handler = createHandler(() => count, (fn) => { count = fn(count); });

handler();
handler();
handler();

console.log(clicks.join(','));

Bug Explained

Bug: handler closes over staleCount at creation (0). Every click computes 0+1=1 instead of incrementing. The function is never recreated with the new count.

Explanation: Functional update receives the actual current value as prev — never reads from a stale closure. Each click gets the latest count.

Key Insight: Always use setState(prev => prev + 1) when new state depends on previous state. This is especially important when the handler is created once (useCallback, event listeners added in useEffect).

More Stale Closures Debug Challenges

MediumsetInterval always reads initial countHarduseCallback stale dep — handler misses latest valueHardClosure captures object reference — works differently than expected

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab