HardMemoization🐛 Debug Challenge

useMemo dep missing — stale filtered list

Buggy Code — Can you spot the issue?

let query = '';
let computeCount = 0;

function useMemo(factory, deps) {
  let cached = null;
  return function get(newDeps) {
    const same = cached && newDeps.every((d,i) => d === cached.deps[i]);
    if (same) return cached.value;
    computeCount++;
    cached = { value: factory(), deps: newDeps };
    return cached.value;
  };
}

const items = ['apple', 'banana', 'apricot', 'blueberry'];

// Bug: items in deps but not query — query changes are ignored
const getFiltered = useMemo(
  () => items.filter(x => x.startsWith(query)), // closes over query
  []
);

const r1 = getFiltered([items]);      // query = ''
query = 'ap';
const r2 = getFiltered([items]);      // query changed but items didn't — cache hit!

console.log(r1.length);
console.log(r2.length); // should be 2 (apple, apricot), returns 4 (stale)
console.log(computeCount);

Fixed Code

let query = '';
let computeCount = 0;

function useMemo(factory, deps) {
  let cached = null;
  return function get(newDeps) {
    const same = cached && newDeps.every((d,i) => d === cached.deps[i]);
    if (same) return cached.value;
    computeCount++;
    cached = { value: factory(), deps: newDeps };
    return cached.value;
  };
}

const items = ['apple', 'banana', 'apricot', 'blueberry'];

// Fix: include query in deps
const getFiltered = useMemo(
  () => items.filter(x => x.startsWith(query)),
  []
);

const r1 = getFiltered([items, query]);   // deps: [items, '']
query = 'ap';
const r2 = getFiltered([items, query]);   // deps: [items, 'ap'] — changed!

console.log(r1.length);
console.log(r2.length);
console.log(computeCount);

Bug Explained

Bug: query is used in the factory but not in deps. When query changes, items stays the same — cache hits and returns the stale result computed with query=''.

Explanation: Including query in deps means the cache misses when query changes. r2 recomputes with the new query 'ap' → ['apple', 'apricot'] = 2 items.

Key Insight: Every variable used inside useMemo's factory must be in the deps array. The exhaustive-deps ESLint rule catches this. Missing deps = stale cached values.

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab