Yashveer Singh
Connect
<- All posts
Performance Optimization12 min read

Why Memoization Is a Trap When Misused

Memoization caches the result of a function so future calls return instantly. Used carefully on expensive, deterministic computations, it is a real win. Used as a reflex on cheap functions or as a fix for re-rendering issues, it adds complexity, increases memory, hides the real problem, and produces stale-data bugs that are nearly impossible to reproduce in development.

Written by Yashveer Singh, founder of Yashveer Labs.

What you actually need to know

  • Memoization moves cost. It does not eliminate it. Memory and comparison are the new costs.
  • Cheap functions get slower when memoized because the bookkeeping costs more than the work.
  • Stale cache bugs are the worst kind: irreproducible, late, and unrelated-looking.
  • React's useMemo and useCallback are not free. Default to neither and earn the right to add them.
  • The right reach is when the function is expensive, the inputs repeat, and the output depends only on the inputs.
SituationShould you memoize?Why
Pure expensive computation, repeated inputsYesReal win, easy to reason about
Cheap pure functionNoBookkeeping costs more than the work
Function reads external stateNoStale-data bug factory
React component renders too oftenSometimesMeasure first, then maybe useMemo
Database query resultUse a real cacheMemoization is the wrong tool, use Redis or similar

The core argument

Memoization gets reached for the same way junior engineers reach for try/catch: it feels safe, it looks like a tool that prevents a class of problem, so the instinct is to apply it broadly. Both habits make code worse in similar ways. They add complexity at every call site to address a problem that might not exist, and they create a different class of bug that is harder to find.

The win from memoization is not free. It is a trade. You spend memory to save time. You spend comparison cost to skip the function body. You spend cognitive cost because the function now has state. If the function was already fast, you paid for those things and saved nothing.

The other quiet cost is that memoization hides the real problem. If a component re-renders too often, memoizing its children stops the renders from being expensive, but the component is still re-rendering too often. You spent a tool on a symptom. The root cause is still there, and now it is harder to see because the renders look cheap.

I prefer to measure first, fix the actual cause, and reach for memoization only when the work is genuinely expensive and the inputs really do repeat. That is rarer than people assume.

The three costs people forget

Memory

Cached results have to live somewhere. For a function called with many distinct input combinations, the cache grows. If you do not bound it, you have a memory leak that looks like a slow application instead of a crash. If you do bound it, you spend cycles on eviction and the cache hit rate is whatever the bound allows.

Comparison

Every memoized call compares the new inputs against the cache. For primitive inputs, comparison is fast. For deep objects, it is not. A React useMemo with a complex dependency array runs the comparison on every render, and if the comparison takes longer than the function it is "optimizing," the app is slower than before.

Staleness

This is the cost that lands months after the code shipped. The function is memoized. The function reads something that can change. The change happens. The function returns the old answer. Nobody can reproduce the bug in development because development always passes fresh inputs. The bug shows up in production and looks like every other intermittent issue you have ever debugged.

What to measure before reaching for it

  • The cost of one call to the function in real conditions.
  • How often the same inputs repeat in production.
  • Whether the function depends on anything outside its explicit arguments.
  • The memory cost of holding the expected cache size.
  • The comparison cost of the inputs you will use as keys.

How much does it cost to get wrong

MistakeWhat it producesTime to discover
Memoize a microsecond functionSlightly slower code, more memoryMaybe never, just slow drift
Memoize a function that reads external stateIntermittent stale-data bugsWeeks to months
Unbounded memo cacheSlow memory leakDays, sometimes the platform restarts and hides it
Memoize the wrong thing in ReactSame render count, more bookkeepingOften noticed by the next engineer
Memoize as a render-count fixSymptom suppressed, cause hiddenWhen the next refactor exposes it

Expert opinion

Memoization is a tool I reach for once a quarter on a real engagement. Most performance problems are not solved by caching. They are solved by removing work, batching it, or moving it to where it can be done in advance. When I do reach for memoization, the function is doing real work, the inputs are well-defined, and the cache has a clear scope. I have removed more memoization in code reviews than I have added, because most of it was hiding cheaper problems.

>

Yashveer Singh, founder of Yashveer Labs

How this played out on a real project

A React app I inherited had useMemo and useCallback everywhere. The previous team had been told the app was slow and had applied memoization as a blanket fix. The app was still slow. Worse, the dependency arrays were inconsistent and a quarter of them were wrong in ways that produced subtle render bugs.

I profiled the app for an hour. The actual cause of the slowness was a single component that recomputed an expensive layout on every parent render because state was lifted too high. Removing the unnecessary memoization and lifting the right piece of state down made the app faster than any combination of caching could have. The work took an afternoon. The lesson is that memoization is a tool for specific problems, not a general performance strategy. The related discipline is documented in how to find the hot path and optimize it.

Common mistakes

  1. Memoizing every function in a hot file as a default.
  2. Adding useMemo to fix a re-render that should have been fixed by lifting state.
  3. Memoizing a function that reads from a database or external API.
  4. Using an unbounded cache and shipping it.
  5. Using a deep equality check as the cache key on a hot path.
  6. Treating memoization as caching and forgetting it has no invalidation strategy.
  7. Skipping measurement and assuming memoization is making things faster.

A two week plan to audit it

  1. Day one. Run a profiler on the slow code path. Identify where time actually goes.
  2. Days two to four. Remove memoization that is not doing measurable work. The code gets simpler and usually faster.
  3. Days five and six. For the genuinely expensive functions, confirm the inputs repeat. If they do not, no memoization will help.
  4. Week two. Add memoization in the few places that earn it. Bound the cache. Make the dependencies explicit.
  5. Week two ongoing. Add a backend performance budget so the next "let's just memoize it" instinct gets caught in review.
  6. Long term. Treat memoization as a tool for measured problems, not a default. The codebase ages better.
FAQ

Frequently asked

Author

Why this work lands with me

I am Yashveer Singh. Founder of Yashveer Labs. I take this kind of project because I have done enough of them to know what kills them. The version of me that writes a post like this is the same one who builds the system afterward. There is no handoff to a junior, no agency middleman, no surprise scope. That is the bet I am making on my own brand.

Related reading