What are the key differences between useMemo and useCallback, and when would you use each?
useMemo memoizes *values* (e.g., computed objects, arrays, or expensive calculations), while useCallback memoizes *functions* to preserve referential identity — both prevent unnecessary re-renders or recomputation when dependencies haven’t changed.
Short Answer
Use useMemo when you need to avoid expensive value computation or object/array recreation on every render; use useCallback when you need to prevent function recreation to satisfy dependency checks (e.g., in useEffect, React.memo, or as props to optimized children).
Details
Both hooks accept a dependency array and return a cached result only if dependencies haven’t changed since the last render. Internally, React compares dependencies with Object.is() — if all items are referentially equal, it returns the cached value/function. useMemo(fn, deps) executes fn() and caches its return value; useCallback(fn, deps) caches the function instance itself. Crucially, useCallback is not for optimizing the function’s execution — it’s for preserving identity so downstream consumers (like React.memo or useEffect) don’t trigger unnecessarily due to new function references.
Example
const Component = ({ items, onItemSelect }) => {
// ✅ useMemo: avoids recreating filtered list & expensive object on every render
const filteredItems = useMemo(() => items.filter(i => i.active), [items]);
// ✅ useCallback: prevents new function reference → avoids re-render in memoized child
const handleSelect = useCallback(
(id) => onItemSelect(id),
[onItemSelect] // critical: includes stable callback to avoid infinite loop
);
return <ItemList items={filteredItems} onSelect={handleSelect} />;
};Bonus
To stand out: clarify that neither hook guarantees performance gains — overuse can hurt readability and memory usage. Always measure with React DevTools Profiler first. Also note: useCallback is often unnecessary if the function doesn’t escape the component (e.g., inline event handlers like onClick={() => doX()} — modern React’s compiler auto-optimizes many cases). Finally, emphasize that dependency arrays must be exhaustive and stable — missing deps cause stale closures, while unstable deps (e.g., {} or [] inline) defeat memoization entirely.