Memoization Pitfalls and Anti-Patterns
Explore the anti-patterns that make memoization slower, not faster, and rebuild your intuition around when it should (and shouldn’t) be used.
In previous lessons, we treated useMemo, useCallback, and React.memo as defensive tools, techniques for protecting components from unnecessary work. In this lesson, we shift perspectives: memoization is a cost, and unless the architecture around it is stable, that cost becomes larger than the work it was meant to save.
The cost equation
Every time you use useMemo, React pays three unavoidable taxes:
Memory cost: React allocates memory for the cached value and the dependency list it must track.
CPU cost: On every render attempt, whether committed or speculative, React runs a shallow comparison of dependencies. Under React 19’s concurrent renderer, speculative renders happen frequently, which amplifies this cost.
Complexity cost: Stale closures, incorrect dependency arrays, and unstable upstream identities can silently invalidate the memo, leaving you with overhead but no benefit.
Golden rule: If the cost of comparing dependencies exceeds the cost of recomputing the value, memoization incurs a net loss.
In React, recomputation is often inexpensive, while dependency comparisons are run on every render attempt, including those that the scheduler later discards. Memoization is also fragile. A single unstable prop, such as an inline object, array, or function, can invalidate an entire memo boundary. When that happens, React pays for memoization and still recomputes the value or re-renders the child. This memo tax is subtle but pervasive in large applications.