Engrain by ByteRush

JS.07.03 Mid-level· 10 minCore — asked often

Memoization: caching results without caching your bugs

Memoization stores a function's results by input. Learn cache keys, eviction, memoizing async functions safely, and when caching makes things worse.

Comes up in: Coding, DSA

Learn#

Suresh’s dashboard shows a user’s name in five widgets. Each widget calls getUser(7), so every page load sends five identical requests. He adds a cache, and now a single network error is stuck on screen until the page reloads.

Memoization stores a function’s results by its inputs, so a repeat call returns the stored answer.

The cache key must capture everything that changes the result. Joining arguments works for simple values; objects need a custom key, such as an ID.

function memoize(fn, key = (...args) => args.join("|")) {
  const cache = new Map();
  return (...args) => {
    const k = key(...args);
    if (!cache.has(k)) cache.set(k, fn(...args));
    return cache.get(k);
  };
}
let calls = 0;
const square = memoize((n) => { calls++; return n * n; });
console.log(square(9), square(9), square(4), "calls:", calls);
const fib = memoize((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)));
console.log(fib(50));

Output (verified)

81 81 16 calls: 2
12586269025

Without the cache, fib(50) makes billions of calls; with it, about fifty.

For async functions, cache the promise, not the result — then calls made while the first is still running share it. And remove the entry if the promise rejects, so one failure is not remembered forever.

Caching a promise, safelyStep 1 of 4

Cache

  • 7 → promise (pending)

Network

  • GET /users/7

Three components ask for user 7 at the same time. The first call starts the fetch and caches its promise.

Read the steps as text
  1. Three components ask for user 7 at the same time. The first call starts the fetch and caches its promise.
  2. Calls two and three find the same promise in the cache. No new requests.
  3. The request fails. The promise rejects, so it is removed from the cache.
  4. The next call starts a fresh request, instead of getting the old failure forever.

Predict#

Predict first

memoize(fetchUser) caches results only after the await finishes. Five widgets call it at the same moment. How many requests are sent?

Interview angle#

Say it like this — about 30 seconds

“Memoization caches a function’s results by a key built from its arguments, so repeat calls skip the work. It is safe for pure functions. The risks are wrong keys, unbounded memory growth, and stale data. For async functions I cache the promise immediately, so concurrent callers share one request, and I delete the entry if it rejects, so errors are retried. I add a size limit or time-to-live, and I memoize only when the work is measurably expensive.”

Stays on your device. Nothing is uploaded.
Follow-up How would you memoize an async function safely?
Store the promise in the cache before awaiting; on rejection, delete that key. Optionally add a time-to-live so data refreshes.
Follow-up How do you stop a memo cache growing forever?
Use an LRU with a maximum size (JS.16.03), a time-to-live, or a WeakMap when the key is an object.
Follow-up When does memoization hurt?
When inputs rarely repeat, when the function is cheap, when it is not pure (time, randomness, shared state), or when the key itself is expensive to build.

Practice#

On your own

The starter caches only after await, and would cache nothing useful on failure. Rewrite memoizeAsync to cache the promise and forget failures.

Done when

  • Three calls in flight with the same key make one call to fn; all get the same value.
  • A later call with that key does not call fn again.
  • After a rejection, the next call calls fn again; different keys are cached separately.
Your solution
Ctrl/⌘ + Enter

Remember#

Try to answer each card before you open it. Then add them to your reviews.

What is memoization?

Caching a function's results by its inputs so repeat calls skip the work.

Why cache the promise rather than the result for async functions?

So calls made while the first is running share it instead of starting their own.

What should happen to a cached promise that rejects?

Remove it from the cache so the next call retries.

Name two risks of memoization.

Unbounded memory growth and stale or wrongly keyed results.

Go deeper

Four layers. Stop at the one your interview needs; each ends with a quick check.

The core ideaEvery round

Same input, same output, no side effects — then memoization is safe. Choose a key that captures every input that matters.

Check yourself: Should you memoize a function that returns the current time?

No. It is not pure; the same input does not give the same output.

How it works underneathSenior rounds

A memoized function is a closure over its cache (JS.02.03). For recursion, the inner calls must use the memoized name — fib above calls the wrapped fib — so every subproblem is computed once. This is top-down dynamic programming (DSA.12) Source: MDN: Memoization.

Check yourself: Why does fib only work when it calls the memoized version of itself?

The recursive calls must go through the cache, or they recompute everything.

Edge cases and trapsWhere people slip

args.join("|") turns every object into “[object Object]”, so different objects collide; use a resolver like (user) => user.id. JSON.stringify keys depend on property order. Eviction — removing old entries — needs a policy: least recently used, time-to-live, or both.

Check yourself: Why does args.join('|') fail for objects?

Every plain object becomes '[object Object]', so different objects share one key.

In productionStaff-level answers

For network data, prefer a data library such as TanStack Query or SWR: they de-duplicate in-flight requests, cache with expiry, retry failures and refresh in the background Source: TanStack Query: Overview. In React, useMemo and memo are memoization too — measure before adding them.

Check yourself: Which libraries handle request de-duplication and caching for React apps?

TanStack Query or SWR.

Finished the five steps? Mark it, and its cards join your reviews.

Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

Interview questions this answers

  • What is memoization? What are its risks?
  • How would you memoize an async function safely?