HardStale Closures🐛 Debug Challenge

Closure captures object reference — works differently than expected

Buggy Code — Can you spot the issue?

const log = [];

// Wrong: spreading creates new object — closure reference is lost
function setup() {
  let user = { name: '' };

  // Handler closes over the SPECIFIC user object at creation
  const handler = () => log.push('user: ' + user.name);

  // Simulate setState — React replaces the object
  user = { ...user, name: 'Alice' }; // NEW object — handler still points to old!
  user = { ...user, name: 'Bob' };   // another new object

  handler(); // reads old user (original empty object)
}

setup();
console.log(log[0]);

Fixed Code

const log = [];

// Correct: use ref pattern — mutation keeps the same reference
function setup() {
  const userRef = { current: { name: '' } };

  // Handler reads from ref — always gets latest
  const handler = () => log.push('user: ' + userRef.current.name);

  // Update via ref — same object, updated property
  userRef.current = { ...userRef.current, name: 'Alice' };
  userRef.current = { ...userRef.current, name: 'Bob' };

  handler(); // reads latest via ref
}

setup();
console.log(log[0]);

Bug Explained

Bug: handler closes over the initial user object reference. React state updates (spread) create new objects — the old reference is stale and still has name: ''.

Explanation: handler reads userRef.current at call time, not at creation time. The ref is the stable container — its contents can change while the reference remains valid.

Key Insight: When you need a callback to always see the latest state without recreating it (e.g., in a long-lived subscription), store state in a ref alongside the actual state: useEffect(() => { latestRef.current = state; }).

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab