If you’ve spent any time writing React, you’ve probably run into useMemo and useCallback and wondered why React needs two separate hooks that seem to do almost the same thing. They’re closely related, they solve the same underlying problem — unnecessary work on re-renders — but they operate on different kinds of values. Understanding that distinction will save you from a lot of confused debugging and premature optimization.
The Problem They Both Solve
Every time a React component re-renders, everything inside its function body runs again. That includes variable declarations, function definitions, and any calculations you’ve written. Most of the time this is fine — JavaScript is fast, and re-running a few lines of code is cheap. But sometimes it isn’t:
- You’re doing an expensive calculation (sorting a large array, filtering a big dataset, running a complex transformation).
- You’re creating a new function or object that gets passed as a prop to a memoized child component, causing that child to re-render even though nothing meaningful changed.
Both useMemo and useCallback exist to let you skip that repeated work by “remembering” (memoizing) a value between renders, and only recalculating it when specific dependencies change.

useMemo: Memoizing Values
useMemo caches the result of a computation. You give it a function that returns a value, plus a dependency array, and React only re-runs that function when one of the dependencies changes.
const sortedItems = useMemo(() => {
return items.slice().sort((a, b) => a.value - b.value);
}, [items]);
Without useMemo, that sort would run on every single render of the component, regardless of whether items changed. With it, the sort only happens when items actually changes — every other render just reuses the cached array.
This is most useful for:
- Expensive calculations (sorting, filtering, aggregating large datasets)
- Derived data that other hooks or child components depend on
- Avoiding recreating objects that would otherwise break reference equality checks
useCallback: Memoizing Functions
useCallback is really just a special case of useMemo — it memoizes a function itself, rather than the result of calling one.
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
Here’s the thing to internalize: in JavaScript, functions are objects, and every time your component re-renders, any function you define inline is a brand new object, even if its code looks identical to the last one. That matters because React (and JavaScript in general) compares objects by reference, not by content. So if you pass a freshly created function as a prop to a child component on every render, that child sees “a new prop” every time — even though functionally nothing changed.
useCallback solves this by returning the same function reference across renders, as long as the dependencies haven’t changed.
This matters most when:
- You’re passing callbacks to memoized child components (wrapped in
React.memo) - The function is a dependency of another hook, like
useEffect— an unstable function reference can cause an effect to fire more often than intended - You’re building custom hooks that return functions, and you want consumers to get a stable reference
The Key Difference, in One Line
useMemo returns the value your function produces. useCallback returns the function itself, without calling it.
In fact, you could technically implement useCallback using useMemo:
// These are functionally equivalent
const memoizedFn = useCallback(fn, deps);
const memoizedFn2 = useMemo(() => fn, deps);
useCallback(fn, deps) is just sugar for useMemo(() => fn, deps). That’s a useful mental model: useMemo is the general tool, and useCallback is a convenience wrapper for the specific, common case of memoizing functions.
Side-by-Side Example
Imagine a parent component that renders an expensive, memoized list, and also computes a derived value:
function ProductList({ products, category }) {
// useMemo: cache the filtered array so it's not
// recalculated unless products or category change
const filteredProducts = useMemo(() => {
return products.filter((p) => p.category === category);
}, [products, category]);
// useCallback: cache the function reference so
// <ExpensiveRow /> doesn't re-render unnecessarily
const handleSelect = useCallback((id) => {
console.log('Selected product', id);
}, []);
return (
<div>
{filteredProducts.map((product) => (
<ExpensiveRow
key={product.id}
product={product}
onSelect={handleSelect}
/>
))}
</div>
);
}
const ExpensiveRow = React.memo(function ExpensiveRow({ product, onSelect }) {
// Expensive rendering logic here
return <div onClick={() => onSelect(product.id)}>{product.name}</div>;
});
Here, useMemo prevents recalculating the filtered list on every render, and useCallback prevents ExpensiveRow from re-rendering just because handleSelect got recreated.
When You Probably Don’t Need Either
This is the part that trips people up: both hooks have a cost. React has to store the previous dependencies and the previous value, and compare them on every render. For cheap calculations or components that aren’t wrapped in React.memo, that bookkeeping can cost more than just letting the recalculation happen.
A good rule of thumb:
- Don’t reach for
useMemounless the calculation is genuinely expensive, or you’re avoiding breaking referential equality for a dependency array elsewhere. - Don’t reach for
useCallbackunless the function is passed to a memoized child, used as a dependency in another hook, or returned from a custom hook. - If you’re not sure whether something is “expensive enough,” it probably isn’t. Profile first, optimize second.
Overusing these hooks is a common source of unnecessary complexity in React codebases — code that’s harder to read, for a performance gain that often doesn’t exist.
Quick Reference
useMemo |
useCallback |
|
|---|---|---|
| Memoizes | A computed value | A function reference |
| Signature | useMemo(() => value, deps) |
useCallback(fn, deps) |
| Common use case | Expensive calculations, derived data | Stable callbacks for child props or hook deps |
| Relationship | The general-purpose hook | Syntactic sugar over useMemo |
The Takeaway
useMemo and useCallback aren’t competing tools — they’re the same tool applied to two different kinds of values: data and functions. Once that clicks, the decision of which one to use mostly makes itself: are you trying to avoid recalculating a value, or avoid recreating a function? Reach for the hook that matches, use it deliberately rather than reflexively, and let profiling — not habit — tell you when it’s actually worth the trouble.