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
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.
This pattern is strongest when:
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.
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:
Memoization goes wrong most often when the team says “same inputs, same outputs” and forgets that outputs also depend on:
Once any of those matter, either the key must include them or memoization is the wrong abstraction.
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.