MemoizeLoading saved progress…

Memoize

Write a higher-order function memoize(fn) that returns a new function with the same behaviour as fn, except results are cached by argument. Repeat calls with the same argument skip fn entirely and return the cached value. This is the classic memoization pattern — a single-argument wrapper backed by a Map, useful for pure functions that are expensive to recompute.

Signature

// Returns a wrapped version of `fn` that caches by its single argument.
// Subsequent calls with an argument already in the cache return the stored
// result and DO NOT invoke `fn` again.
function memoize<A, R>(fn: (arg: A) => R): (arg: A) => R;

Examples

let calls = 0;
const square = memoize((n) => {
  calls++;
  return n * n;
});

square(5);   // 25  (calls === 1)
square(5);   // 25  (calls === 1 — cache hit, fn not invoked)
square(6);   // 36  (calls === 2 — distinct arg, fn invoked once more)
square(5);   // 25  (calls === 2 — still cached)
// Different argument types each get their own cache entry.
const id = memoize((x) => x);

id(1);            // 1
id('1');          // '1'    (a separate slot — string '1' !== number 1)
id(null);         // null
id(undefined);    // undefined
id({ a: 1 });     // { a: 1 } (cached by reference, not by shape)

Notes

  • Single-argument only. memoize wraps unary functions. Don't try to handle (a, b) here — that's a multi-arg variant and is out of scope for this question.
  • Cache by Map, keyed on the argument as-is. Use new Map() (not a plain object). The argument is the key directly — no JSON.stringify, no coercion. That means lookup is reference equality for objects/arrays, value equality for primitives.
  • Don't re-invoke fn on a cache hit. Even if the cached value is undefined, a second call with the same argument must return the stored undefined without calling fn. Use cache.has(key) to test presence, not cache.get(key) (which can't distinguish "missing" from "stored undefined").
  • No eviction. The cache grows for the lifetime of the returned function. That's the tradeoff of this v1 — you trade memory for speed. Bounded-size variants (LRU) are a Going further topic.
  • Preserve return value. The memoized function returns exactly what fn returned the first time for that argument — no cloning, no freezing.
  • Don't worry about this. Tests call the memoized function as a plain function. You can ignore Function.prototype.call/apply binding for this exercise.
Loading editor…