Overusing React.memo and useMemo Is a Red Flag
useMemo everywhere is not making your app faster.
Somewhere a rule of thumb turned into a habit: wrap every component in React.memo, every derived value in useMemo, every callback in useCallback, just in case. It looks like performance work. Mostly it's noise. Memoization isn't free, and applying it blindly can make an app slower and definitely makes it harder to read. Overusing memo is as much a red flag as never using it.
Memoization has a cost
Every memo does bookkeeping so it can skip work later:
useMemo/useCallbackstore the dependency array and the cached value, then compare the deps on every render. If the computation was cheap, you've replaced a cheap operation with a cheap operation plus an allocation and a comparison. That can be a net loss.React.memoruns a props comparison before every potential render. If any prop is a fresh object, array, or inline function each render, the comparison always fails and you pay for it while gaining nothing.
So the mental model "memo = faster" is wrong. Memo trades comparison work now to avoid recompute work later. It only pays off when the avoided work is genuinely expensive and actually avoided.
Measure first, then memoize
The right order is measure, then optimize:
- Profile. Use the React Profiler (or the DevTools flamegraph) to find the components that actually re-render often and cost real time. Record an interaction that feels slow.
- Find the real bottleneck. It's usually a small number of components or one expensive computation, not the whole tree.
- Memoize only that. Apply
useMemoto the genuinely expensive calculation,React.memoto the component that re-renders needlessly with stable props. Leave the rest alone.
// Red flag: memoizing a trivial value "just in case"
const label = useMemo(() => `${first} ${last}`, [first, last]); // pointless
// Justified: the profiler showed this sort dominates render time
const sorted = useMemo(
() => bigList.slice().sort(expensiveComparator), // real cost, avoided
[bigList]
);
What good looks like
| Habit | Signal |
|---|---|
| Memoize everything by default | Red flag: added complexity, comparison cost, no measured win |
| Memoize what a profile proved slow | Green flag: targeted, defensible, still readable |
| Reach for memo before profiling | Red flag: optimizing blind |
The payoff of measuring first is less noise, real wins, and code that stays readable. A memo you can point to a flamegraph and justify is worth it. One you added out of superstition is just tech debt that looks like diligence.
Power moves
- Let the profiler nominate candidates. Never memoize a component you haven't seen misbehave in a recording.
- Fix the cause, not the symptom. Often the real problem is an unstable prop created in the parent; stabilize it once instead of wrapping every child in
React.memo. - Remember the compiler. The React Compiler auto-memoizes at build time, which makes hand-rolled
useMemo/useCallbacklargely redundant going forward. Manual memo is increasingly a smell, not a badge. - Delete unjustified memos in review. If nobody can name the measurement that motivated a memo, take it out. Readability is a performance feature for the team.
Resources
- React docs —
useMemo("Should you add useMemo everywhere?") - React docs —
React.memo - React docs — Profiler and measuring performance
- React Compiler — automatic memoization
Shipping AI or cloud systems? Yeda AI audits and hardens production LLM and cloud pipelines. Talk to us · Read the blog