Q: What's the difference between useMemo and useCallback? When are they not worth using?
Answer:
Both useMemo and useCallback are React Hooks used for performance optimization (memoization). They both take a dependency array and re-compute only when those dependencies change.
Their core difference lies in what they return:
useMemocaches the result of a function calculation.useCallbackcaches the function itself (the reference to the function).
Essentially, useCallback(fn, deps) is exactly equivalent to useMemo(() => fn, deps).
1. useMemo Use Cases
Use it to avoid repeating expensive, time-consuming calculations on every single render.
// ✅ GOOD USE CASE: Expensive calculation
const expensiveResult = useMemo(() => {
return bigArray.filter(item => item.value > threshold).map(heavyTransformation);
}, [bigArray, threshold]);
2. useCallback Use Cases
Use it when you need to keep a function reference completely stable between renders. This is almost exclusively needed when you are passing a callback function as a prop to a deeply nested child component that is wrapped in React.memo(), or if the function is used in a useEffect dependency array.
// ✅ GOOD USE CASE: Passing to a pure child component
const handleSubmit = useCallback((data) => {
api.submit(data);
}, []); // Function reference never changes
// MemoizedChild will NOT re-render since `handleSubmit` is identical across renders
return <MemoizedChild onSubmit={handleSubmit} />;
When is it NOT worth it? (The "Gotcha")
A massive red flag in interviews is developers who wrap every function in useCallback and every variable in useMemo.
React components are designed to tear down and rebuild extremely fast. Over-memoizing actually hurts performance because caching results and tracking dependency arrays takes up more memory and execution time than just letting React do its normal job.
DON'T use them when:
- The operation is cheap: Doing basic math or string concatenation? Don't use
useMemo.const fullName = firstName + lastNameis thousands of times faster to recalculate than wrapping it inuseMemo. - Passing to native HTML elements: Wrapping a function in
useCallbackjust to pass it to<button onClick={handleClick}>is completely useless. Native DOM elements don't care about reference equality. - The child isn't memoized: If you pass a
useCallbackfunction to a custom<Button>component, but that<Button>component is NOT wrapped inReact.memo, the child is going to re-render anyway. You just wasted memory caching the function!
[!TIP] Interview Rule of Thumb: Write the code without
useMemooruseCallbackfirst. Only add them if you explicitly identify a performance bottleneck (like a 500ms lag on typing) or auseEffectinfinite loop caused by an unstable function dependency.