MediumHook Patterns🐛 Debug Challenge

useEffect missing cleanup — subscription leaks

Buggy Code — Can you spot the issue?

const subscribers = new Set();
const log = [];

function subscribe(handler) {
  subscribers.add(handler);
  return () => subscribers.delete(handler);
}

function publish(value) {
  subscribers.forEach(h => h(value));
}

// Simulates useEffect without cleanup
function simulateComponent(renders) {
  for (let i = 0; i < renders; i++) {
    // No cleanup — adds new subscriber on every render
    subscribe(v => log.push('render' + i + ':' + v));
  }
}

simulateComponent(3); // 3 renders
publish('hello');
console.log(log.length); // how many times was hello received?
console.log(log.join(','));

Fixed Code

const subscribers = new Set();
const log = [];

function subscribe(handler) {
  subscribers.add(handler);
  return () => subscribers.delete(handler);
}

function publish(value) {
  subscribers.forEach(h => h(value));
}

// Simulates useEffect WITH cleanup
function simulateComponent(renders) {
  let unsubscribe = null;
  for (let i = 0; i < renders; i++) {
    if (unsubscribe) unsubscribe(); // cleanup previous
    unsubscribe = subscribe(v => log.push('handler:' + v));
  }
}

simulateComponent(3); // 3 renders, but only 1 active subscriber
publish('hello');
console.log(log.length);
console.log(log[0]);

Bug Explained

Bug: No cleanup function returned. Each render adds a new subscription. After 3 renders, there are 3 active subscribers — 'hello' is received 3 times.

Explanation: Each render cleans up the previous subscription before adding a new one. After 3 renders, only the latest subscription is active.

Key Insight: Always return a cleanup function from useEffect when subscribing to any source (WebSocket, EventEmitter, store). Missing cleanup = memory leak + stale handlers receiving events after component unmounts.

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab