Overview
useMemo is a React Hook that caches the result of a pure calculation between renders until its dependencies change. It is mainly a performance optimization. It can help skip expensive recalculations, provide stable derived values to memoized children, or avoid recreating dependency values used by other hooks.
function ProductList({
products,
query,
}: {
products: Product[];
query: string;
}) {
const visibleProducts = useMemo(
() => filterProducts(products, query),
[products, query]
);
return <List items={visibleProducts} />;
}
Dependency correctness means the dependency array includes every reactive value used by the memoized calculation. Reactive values include props, state, and variables or functions declared inside the component. React compares dependencies with Object.is; if none changed, React can reuse the cached result.
For interviews, this topic matters because many candidates overuse useMemo, omit dependencies, depend on unstable objects, or treat memoization as a correctness tool. A strong answer explains what useMemo does, when it helps, when it is unnecessary, how dependency arrays work, and why memoized calculations must stay pure.
The practical goal is to use memoization deliberately: fix data flow first, measure or identify real cost, then memoize the smallest useful pure calculation with correct dependencies.
Core Concepts
What useMemo Does
useMemo caches a calculation result.
const result = useMemo(() => calculate(input), [input]);
It takes:
- A calculation function that takes no arguments and returns a value.
- A dependency array containing every reactive value used inside the calculation.
On the initial render, React calls the calculation. On later renders, React compares dependencies with the previous render. If dependencies are the same by Object.is, React returns the cached value. If any dependency changed, React runs the calculation again.
useMemo Is a Performance Optimization
The component should still be correct without useMemo.
This should be correct:
const visibleTodos = filterTodos(todos, tab);
Then this may improve performance:
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
If removing useMemo breaks behavior, the component has a design problem. State, refs, or clearer data flow may be the correct tool.
Use useMemo for:
- Expensive pure calculations.
- Stable props for a component wrapped in
memo. - Stable values used as dependencies of other hooks.
Avoid using it as a blanket optimization around every value.
Dependency Arrays
The dependency array must include every reactive value used in the calculation.
const visibleProducts = useMemo(
() => products.filter((product) => product.name.includes(query)),
[products, query]
);
products and query are dependencies because the calculation reads them.
This is wrong:
const visibleProducts = useMemo(
() => products.filter((product) => product.name.includes(query)),
[products]
);
If query changes, the memoized value may stay stale.
The dependency list should be inline and have a constant number of items:
[products, query]
Do not build dependency arrays dynamically.
What Counts as a Reactive Value
Reactive values include:
- Props.
- State.
- Context values.
- Variables declared inside the component body.
- Functions declared inside the component body.
- Values returned by hooks.
Example:
function SearchResults({
products,
query,
}: {
products: Product[];
query: string;
}) {
const normalizedQuery = query.trim().toLowerCase();
const visibleProducts = useMemo(() => {
return products.filter((product) =>
product.name.toLowerCase().includes(normalizedQuery)
);
}, [products, normalizedQuery]);
return <List items={visibleProducts} />;
}
normalizedQuery is reactive because it is declared in the component body and changes when query changes. You can also move it inside the calculation and depend on query directly.
const visibleProducts = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
return products.filter((product) =>
product.name.toLowerCase().includes(normalizedQuery)
);
}, [products, query]);
This is often clearer.
Dependency Comparison Uses Identity
React compares dependencies with Object.is.
Primitive values compare by value:
const count = 1;
const query = "react";
Objects, arrays, and functions compare by reference:
const options = { matchMode: "whole-word", query };
If options is created during render, it is a new object every render. Depending on it defeats memoization:
const options = { matchMode: "whole-word", query };
const results = useMemo(
() => searchProducts(products, options),
[products, options]
);
Better:
const results = useMemo(() => {
const options = { matchMode: "whole-word", query };
return searchProducts(products, options);
}, [products, query]);
Now the calculation depends on stable primitive inputs instead of a new object reference.
Expensive Calculations
Most calculations are not expensive enough to need useMemo.
Usually fine:
const fullName = `${firstName} ${lastName}`;
const isValid = email.includes("@") && password.length >= 8;
Potentially worth memoizing:
- Filtering or sorting thousands of items.
- Expensive data transformation.
- Heavy parsing.
- Calculating layout data.
- Creating a large derived tree.
Measure when unsure:
console.time("filter products");
const visibleProducts = filterProducts(products, query);
console.timeEnd("filter products");
Profile production builds when possible. Development mode and Strict Mode can make timings misleading.
useMemo vs Derived State
Do not store derived data in state just to avoid recalculation.
Bad:
const [visibleProducts, setVisibleProducts] = useState<Product[]>([]);
useEffect(() => {
setVisibleProducts(filterProducts(products, query));
}, [products, query]);
Better:
const visibleProducts = filterProducts(products, query);
If it is expensive:
const visibleProducts = useMemo(
() => filterProducts(products, query),
[products, query]
);
The source of truth remains products and query. The memoized value is only a cached calculation result.
useMemo and memo
memo can skip re-rendering a child when its props are unchanged. useMemo can help keep a derived prop stable.
const ProductTable = memo(function ProductTable({
rows,
}: {
rows: ProductRow[];
}) {
return <Table rows={rows} />;
});
Parent:
function ProductPage({ products, query, theme }: Props) {
const rows = useMemo(
() => buildRows(products, query),
[products, query]
);
return <ProductTable rows={rows} />;
}
If theme changes but products and query do not, rows can keep the same reference, so ProductTable may skip re-rendering.
Without useMemo, buildRows might create a new array every render, making memo less useful.
useMemo for Hook Dependencies
Sometimes a value is used as a dependency of another hook.
Problem:
const options = {
serverUrl,
roomId,
};
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]);
The object is new every render, so the effect reconnects too often.
One fix:
const options = useMemo(
() => ({ serverUrl, roomId }),
[serverUrl, roomId]
);
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]);
Often better:
useEffect(() => {
const options = { serverUrl, roomId };
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]);
Move object creation inside the effect when the object is only needed there.
useMemo vs useCallback
useMemo caches a value. useCallback caches a function reference.
These are equivalent in spirit:
const handleSubmit = useMemo(() => {
return (order: Order) => {
submitOrder(productId, order);
};
}, [productId]);
const handleSubmit = useCallback((order: Order) => {
submitOrder(productId, order);
}, [productId]);
Use useCallback for functions because it avoids the extra nested function shape.
Only stabilize function references when it matters:
- Passing a callback to a memoized child.
- Using a callback as a dependency of another hook.
- Returning stable callbacks from a custom hook.
Pure Calculations
The useMemo calculation runs during rendering, so it must be pure.
Bad:
const visibleTodos = useMemo(() => {
todos.push({ id: "new", text: "Mutated" });
return filterTodos(todos, tab);
}, [todos, tab]);
This mutates a prop during render.
Good:
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab);
}, [todos, tab]);
In development Strict Mode, React may call the calculation more than once to help expose accidental impurities. Pure calculations are safe under repeated calls.
Cache Is Not a Semantic Guarantee
useMemo is allowed to discard its cached value for specific reasons, such as development edits or initial mount suspension. This is fine when useMemo is only a performance optimization.
Do not use useMemo to store information that must persist for correctness.
Use state when changes should trigger rendering:
const [selectedId, setSelectedId] = useState<string | null>(null);
Use a ref when mutable data must persist without causing renders:
const latestRequestId = useRef(0);
Use useMemo only for recalculable values.
React Compiler Nuance
Modern React tooling is moving toward more automatic memoization through the React Compiler. That reduces the need for manual useMemo in many cases when the compiler is available and enabled.
Interview-friendly answer:
- Understand manual
useMemobecause many codebases still use it. - Do not add
useMemoeverywhere. - Keep render logic pure.
- Prefer clear data flow.
- Let compiler/tooling handle routine memoization where the project supports it.
The principles of dependency correctness and purity still matter.
Common Mistakes
Common mistakes include:
- Using
useMemofor every object or calculation. - Omitting dependencies to avoid recalculation.
- Depending on an object or function created during render.
- Using
useMemoto make broken code work. - Mutating props or state inside the memoized calculation.
- Storing derived values in state instead of calculating them.
- Expecting
useMemoto prevent all child renders withoutmemo. - Measuring performance only in development mode.
- Using
useMemowhereuseCallbackcommunicates intent better. - Depending on the cache as persistent storage.
Best Practices
Use these rules of thumb:
- Write correct code first without memoization.
- Memoize only expensive pure calculations or identity-sensitive values.
- Include every reactive value used by the calculation.
- Prefer primitive dependencies when possible.
- Move object creation inside the memoized calculation.
- Move object creation inside effects when the object is effect-only.
- Use
useCallbackfor function references. - Treat
useMemoas a performance optimization, not storage. - Measure before optimizing when the cost is uncertain.