All questions

Memoize II

Premium

Memoize II

Implement a memoize helper that caches the return value of a function based on the full argument list it was called with — any count, any types. A single-argument memoizer is a one-liner (new Map().get(arg)); the variadic version is where the interesting choices live. On a cache hit, the original function must not run a second time.

Signature

// Returns a wrapped function that caches return values by the
// exact sequence of arguments it was called with.
function memoize<F extends (...args: any[]) => any>(fn: F): F;

Examples

let calls = 0;
const add = memoize((a, b) => {
  calls++;
  return a + b;
});

add(1, 2); // 3
add(1, 2); // 3  (cache hit — calls still 1)
add(2, 1); // 3  (different arg order — calls now 2)
// Distinct arities are distinct cache entries.
const sum = memoize((...nums) => nums.reduce((a, n) => a + n, 0));
sum(1, 2);     // 3, runs
sum(1, 2, 3);  // 6, runs (different argument count)
sum(1, 2);     // 3, cache hit
sum();         // 0, runs (zero-arg call has its own slot)
// Object args are cached by REFERENCE, not by shape.
const ident = memoize((obj) => ({ ...obj }));
const a = { id: 1 };
ident(a);          // runs
ident(a);          // cache hit (same reference)
ident({ id: 1 });  // runs (different object, same shape)

Notes

  • Variadic. The wrapped function accepts any number of arguments. Calling with (1, 2) and (1, 2, 3) must be distinct cache entries.
  • Reference equality for non-primitives. Two distinct objects (or arrays, or functions) are not the same key even if they look identical. Mirror how Map treats keys.
  • Primitive distinctness preserved. 0 and '0' are different keys. null and undefined are different keys. NaN matches NaN (Map-style key equality).
  • Cache undefined returns too. A function whose result is undefined should still be a cache hit on the second call — don't probe with cache.get(...) === undefined to decide whether to recompute.
  • Each call to memoize produces a fresh cache. Two memoized wrappers of the same function don't share entries.
  • Don't rely on JSON.stringify. Two different references with the same JSON shape would collide, cyclic objects would throw, and functions/undefined would silently disappear from the key. Use a structure that respects reference identity.
  • this-binding and side effects are out of scope. Assume fn is pure and called as a plain function.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium