Function Memoization

Caching deterministic function outputs by input signature and on the hidden dependencies that can make memoization unsafe.

Function memoization caches the output of a function based on its input signature so the same computation does not need to run repeatedly. It is one of the cleanest forms of caching when the function is deterministic, side-effect-free, and expensive enough that reuse matters. In those cases, memoization can turn repeated compute into simple lookup with very little conceptual overhead.

The danger is that many functions are not as pure as they first appear. A function may depend on time, environment, locale, feature flags, user permissions, remote configuration, or hidden mutable state. Once those dependencies exist, memoization is no longer only about input arguments. The cache contract must reflect the real dependency set or the cached result may become subtly wrong.

    flowchart LR
	    A["Function inputs"] --> B{"Memoized key"}
	    B -->|Hit| C["Reuse computed result"]
	    B -->|Miss| D["Run expensive function"]
	    D --> C
	    E["Hidden dependencies"] -. "can make key incomplete" .-> B

Why It Matters

Memoization matters because expensive computation often hides in ordinary code paths. Parsing, formatting, transformation, scoring, policy evaluation, or compilation-like work may not look like “cache candidates” at first, but repeated invocation with the same inputs can consume a surprising amount of time. Memoization is often the lowest-friction way to remove that cost.

Where Memoization Works Well

This pattern is strongest when:

  • the function is deterministic for the chosen inputs
  • the result is reused frequently
  • the cost of the function is higher than the lookup overhead
  • memory growth is bounded or eviction is well understood

It is weaker when the function depends on hidden state or when the input space is so large that memoization turns into unbounded memory accumulation.

Example

This example memoizes a pricing-band calculation. The important part is not the business formula. It is that the result is deterministic for the explicit arguments.

 1type PriceBand = "budget" | "standard" | "premium";
 2
 3function memoize<K extends string, V>(fn: (key: K) => V): (key: K) => V {
 4  const store = new Map<K, V>();
 5  return (key: K) => {
 6    if (store.has(key)) {
 7      return store.get(key)!;
 8    }
 9    const value = fn(key);
10    store.set(key, value);
11    return value;
12  };
13}
14
15const classifyPriceBand = memoize((key: string): PriceBand => {
16  const cents = Number(key);
17  if (cents < 2_000) return "budget";
18  if (cents < 10_000) return "standard";
19  return "premium";
20});

What to notice:

  • the cache key is derived directly from the real input
  • the function is pure for that key
  • there is no freshness problem unless the hidden rules change outside the explicit input

Hidden Dependency Risk

Memoization goes wrong most often when the team says “same inputs, same outputs” and forgets that outputs also depend on:

  • current time
  • locale or region
  • feature flags
  • remote policy data
  • mutable global state

Once any of those matter, either the key must include them or memoization is the wrong abstraction.

Common Mistakes

  • memoizing functions that depend on clocks or mutable globals
  • letting the memoization map grow without bounds
  • assuming a function is pure because it has no obvious I/O calls
  • memoizing results whose dependencies change more often than reuse justifies

Design Review Question

What is the first question to ask before memoizing an expensive function?

The stronger answer is not how slow it is. It is whether the output is truly determined by the inputs you plan to key on. If hidden dependencies matter, the cache contract is already incomplete.

Quiz Time

Loading quiz…
Revised on Thursday, April 23, 2026