HardHook Patterns🐛 Debug Challenge

Custom hook shares state with multiple callers — should not

Buggy Code — Can you spot the issue?

// Bug: state is shared across all instances of the hook
const sharedCache = { count: 0, label: '' };

function useCounter(label) {
  sharedCache.label = label; // shared — last writer wins!
  return {
    get: () => sharedCache.count,
    inc: () => { sharedCache.count++; },
    label: () => sharedCache.label,
  };
}

const a = useCounter('counterA');
const b = useCounter('counterB');

a.inc(); a.inc();
b.inc();

console.log(a.get());   // should be 2
console.log(b.get());   // should be 1
console.log(a.label()); // should be counterA

Fixed Code

function useCounter(label) {
  // Each call gets its own private state via closure
  let count = 0;
  const savedLabel = label;
  return {
    get:   () => count,
    inc:   () => { count++; },
    label: () => savedLabel,
  };
}

const a = useCounter('counterA');
const b = useCounter('counterB');

a.inc(); a.inc();
b.inc();

console.log(a.get());
console.log(b.get());
console.log(a.label());

Bug Explained

Bug: sharedCache is declared outside the hook — a module-level singleton. Both a and b share the same object. b.inc() increments the same count. a.label() reads counterB because b was initialized last.

Explanation: Each useCounter() call creates a fresh closure with its own count and savedLabel variables. a and b are completely independent.

Key Insight: Custom hooks share LOGIC, not STATE. Every call to a custom hook creates independent state. Module-level variables are singletons — never use them for per-instance state.

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab