How do these behave inside a useCallback
useCallback memoizes a function so its reference is stable across renders — unless a dependency changes. Closures inside it capture values from the render where it was created; stale deps mean stale values. The function identity matters for child memoization and effect deps.
useCallback(fn, deps) returns the same function reference between renders as long as deps are unchanged. The behaviors interviewers probe:
1. Closures capture the render's values
The function you pass closes over variables from the render where it was created. If a value isn't in deps, the callback keeps seeing the stale version:
const [count, setCount] = useState(0);
const log = useCallback(() => {
console.log(count); // captures count from the render this was created in
}, []); // ❌ empty deps → always logs 0Add count to deps and the callback is recreated when count changes, capturing the fresh value. Stale-closure bugs are the #1 thing being tested here.
2. The functional updater escape hatch
If you only need the latest state, not to render on it, use the updater form — then you can keep deps empty safely:
const increment = useCallback(() => {
setCount((c) => c + 1); // no dependency on count
}, []);3. Reference stability — why it exists
The point of useCallback isn't speed of the function — it's a stable reference:
- Passed to a
React.memochild as a prop → child doesn't re-render needlessly. - Used in another hook's dependency array (
useEffect,useMemo) → effect doesn't re-fire every render.
Without useCallback, a new function is created every render — a new reference — defeating memo and retriggering effects.
4. It does nothing if deps change every render
const handler = useCallback(() => {...}, [{}]); // new object each render → never stableIf a dep is itself unstable, useCallback is pointless overhead.
5. It's a hint, not a guarantee
React may discard the memoized function. Don't rely on identity for correctness — only for optimization.
The framing
"useCallback gives you a stable function reference until deps change. Two things to watch: closures capture values from the creation render, so missing deps cause stale-closure bugs — fix with correct deps or the functional updater. And it only earns its keep when the reference actually matters: a memo'd child prop or another hook's dep array. Wrapping every function in it is cargo-culting."
Follow-up questions
- •What's the difference between useCallback and useMemo?
- •When does useCallback actually improve performance?
- •How does the functional updater form avoid a dependency?
- •Why might wrapping everything in useCallback be harmful?
Common mistakes
- •Empty deps with a callback that reads state/props — classic stale closure.
- •Wrapping every function in useCallback without a memo'd consumer.
- •Putting an unstable object/array in deps, making memoization useless.
- •Relying on reference identity for correctness, not just optimization.
Performance considerations
- •useCallback itself has a cost — it stores the function and compares deps every render. It only pays off when the stable reference prevents a more expensive re-render or effect. Used everywhere blindly, it's net-negative.
Edge cases
- •A dependency that's a new object literal every render.
- •Callback used in a useEffect dep array causing an effect loop.
- •React discarding the memoized callback (allowed by spec).
Real-world examples
- •A callback passed to a memoized list-row component to prevent re-rendering thousands of rows.
- •A fetch function used as a useEffect dependency, stabilized so the effect runs once.