useMemo
Topic Category
- React (Performance, memoization)
- JavaScript (Reference equality, objects/arrays, closures)
- TypeScript (Type inference, generics, safe dependencies)
1. What is useMemo?
useMemo lets you cache a computed value between renders.
const value = useMemo(() => computeSomething(), [deps]);
- React runs your
computeSomethingon the first render. - On later renders, React reuses the old cached value if dependencies did not change.
- If a dependency changed, React recomputes and stores the new result. (React)
2. The simplest mental model
A component re-renders often. Anything you write directly in the component body runs again:
const filtered = filterTodos(todos, tab); // runs on every render
With useMemo, you tell React:
“Do not redo this calculation unless inputs changed.”
const filtered = useMemo(() => filterTodos(todos, tab), [todos, tab]);
3. Real-life analogy (noob-friendly)
Imagine you are making tea.
- Without
useMemo: every time a guest asks “what’s the tea?”, you boil water again from scratch. - With
useMemo: you keep the tea ready in a thermos. You only boil again when the tea leaves or water changes.
Key point:
- It is not about correctness.
- It is about avoiding repeated work.
4. How React decides “dependency changed” (important)
React compares each dependency using Object.is. (React)
That means:
- Primitives (number/string/boolean) are compared by value.
- Objects/arrays/functions are compared by reference, not by “looks the same”.
JavaScript reference equality example
const a = { x: 1 };
const b = { x: 1 };
a === b; // false (different objects)
const c = a;
a === c; // true (same reference)
Objects are reference types: two distinct objects are never equal even with same properties. (MDN Web Docs)
This is the biggest reason useMemo and memo “don’t work” for beginners.
5. When you should use useMemo
Use case A: Expensive calculation on every render
Example: filtering a big list.
function TodoList({ todos, tab }: { todos: string[]; tab: string }) {
const visibleTodos = useMemo(() => {
return todos.filter(t => (tab === "all" ? true : t.includes(tab)));
}, [todos, tab]);
return (
<ul>
{visibleTodos.map((t, i) => (
<li key={i}>{t}</li>
))}
</ul>
);
}
This matches the official pattern: cache a calculation until dependencies change. (React)
Use case B: Create stable objects/arrays passed to memoized children
Problem: objects are new on every render.
const options = { pageSize: 20, sort: "desc" }; // new object every render
<Child options={options} />
Even if values are same, options reference changes -> child sees prop changed.
Fix:
const options = useMemo(() => {
return { pageSize: 20, sort: "desc" };
}, []);
<Child options={options} />
Use case C: Keep effect dependencies stable
Sometimes you depend on an object inside useEffect:
useEffect(() => {
fetchData(searchOptions);
}, [searchOptions]);
If searchOptions is created inline, the effect runs every render.
Fix using useMemo:
const searchOptions = useMemo(() => {
return { text, matchMode: "whole-word" };
}, [text]);
useEffect(() => {
fetchData(searchOptions);
}, [searchOptions]);
6. When you should NOT use useMemo
Rule from React docs
You should only rely on useMemo as a performance optimization. If your code breaks without it, fix the real issue first. (React)
Do not use useMemo to:
- “Store state”
- “Keep values forever”
- “Guarantee compute runs only once”
React may throw away the cache for specific reasons (example: edits in development, or if a component suspends during initial mount). (React)
7. Why your memoized calculation might run twice in development
In React Strict Mode (development only), React may call your calculation function twice to help you find impure logic. One result is ignored. (React)
So if you do:
const value = useMemo(() => {
console.log("calc");
return heavyWork();
}, [a, b]);
You may see "calc" printed twice in dev. That is expected.
8. Purity rule (very important)
Your useMemo function should be pure:
- Do not mutate props/state objects inside it.
- Do not do side effects (API calls, setState, DOM changes).
React explicitly recommends purity, and Strict Mode double-call helps reveal impurities. (React)
Bad:
const value = useMemo(() => {
todos.push("new"); // mutating a prop
return todos.length;
}, [todos]);
Good:
const value = useMemo(() => {
const copy = [...todos];
copy.push("new");
return copy.length;
}, [todos]);
9. useMemo vs useCallback (simple)
useMemocaches a valueuseCallbackcaches a function reference
React docs show they are closely related; memoizing a function via useMemo works but is more verbose. (React)
Equivalent idea
const fn1 = useCallback(() => doSomething(a), [a]);
const fn2 = useMemo(() => {
return () => doSomething(a);
}, [a]);
Prefer useCallback for functions; prefer useMemo for values.
10. TypeScript patterns for useMemo
Pattern A: Let TypeScript infer
const stats = useMemo(() => {
return { total, completed };
}, [total, completed]);
Pattern B: Explicit generic (useful for complex return types)
type Stats = { total: number; completed: number };
const stats = useMemo<Stats>(() => {
return { total, completed };
}, [total, completed]);
Pattern C: Memoizing arrays safely
const ids = useMemo(() => items.map(i => i.id), [items]);
Be careful: items must be stable or intentionally included. If items is a new array each render, memoization won’t help.
11. Older React vs newer React
Older React docs mindset
useMemo existed mainly as “avoid expensive recalculation”. The legacy docs describe it as recomputing only when deps change (but note the legacy page is marked out of date). (React)
Modern React (18/19 era)
Modern docs add key caveats:
- Strict Mode may run calculation twice in dev. (React)
- Cache can be thrown away for specific reasons. (React)
useMemois an optimization, not a correctness tool. (React)
New direction: React Compiler
React Compiler aims to do memoization automatically in many cases. React still keeps useMemo/useCallback as an “escape hatch” when you need precise control (for example, stable effect dependencies). (React)
12. Common mistakes checklist
- Using
useMemofor correctness (wrong) - Missing dependencies (stale values)
- Depending on objects/arrays that are recreated every render (memo becomes useless)
- Doing side effects inside
useMemo(wrong) - Expecting
useMemoto keep values across unmounts (it won’t) - Adding
useMemoeverywhere (adds complexity and overhead)
13. Interview questions (most common)
- What does
useMemodo in React? - What problem does
useMemosolve? - How does React decide whether dependencies changed?
- Why does
useMemooften “not work” with objects and arrays? - Why can
useMemorun twice in development? - Can React discard the memoized value? When?
- What’s the difference between
useMemoanduseCallback? - When should you avoid using
useMemo? - How does
useMemohelp withReact.memo? - What is a “pure function” in the context of
useMemo? - How do missing dependencies create stale bugs?
- How does React Compiler change the need for manual memoization?
References
- React Docs:
useMemo(behavior, dependencies withObject.is, Strict Mode double call, cache caveats, “optimization only”). (React) - React Docs:
StrictMode(development-only extra checks). (React) - React Docs: React Compiler introduction (recommend relying on compiler, keep
useMemo/useCallbackas escape hatch). (React) - MDN: Working with objects (objects are reference types; equality is by reference). (MDN Web Docs)
- MDN: Functions (functions are first-class objects). (MDN Web Docs)
If you want, paste one of your components where “memo is not working” and I’ll mark exactly which dependency (or which inline object/JSX) is breaking memoization and how to fix it with useMemo (simple + advanced).