React Hooks Interview Deep-Dive — useEffect, useMemo, useCallback Gotchas
A practical React hooks interview deep-dive covering useEffect dependencies, stale closures, useMemo and useCallback tradeoffs, Strict Mode behavior, and how to explain hooks under pressure.
React Hooks Interview Deep-Dive — useEffect, useMemo, useCallback Gotchas
This React hooks interview deep-dive focuses on the questions that separate “I have used hooks” from “I understand the render model.” The biggest gotchas are useEffect dependency arrays, stale closures, useMemo misuse, useCallback cargo culting, cleanup timing, and Strict Mode double-invocation in development. Interviewers ask these because hooks sit at the boundary between JavaScript closures and React's declarative rendering model.
The short version: hooks are not lifecycle methods with new names. They are a way to synchronize state, props, derived values, refs, and external systems across renders. If you can explain what each render sees and when an effect synchronizes with the outside world, most hook questions become manageable.
The mental model: every render has its own values
A React component function runs to produce a render. The variables inside that function belong to that render. Event handlers and effects close over those values. A later render creates new values; old closures do not magically update.
That is why stale closures happen:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(id);
}, []);
}
This logs the initial count forever because the effect runs once and the interval callback closes over the first render's count.
Fixes depend on intent:
- Include
countin dependencies if the interval should resubscribe whenevercountchanges. - Use a functional state update if you only need to update based on previous state.
- Use a ref if an external callback needs the latest value without resubscribing.
Interviewers want to hear that you choose based on synchronization needs, not that you silence the lint rule.
useEffect is for synchronization, not derivation
Use useEffect when the component needs to synchronize with something outside React: network requests, subscriptions, timers, DOM APIs, analytics, browser storage, or imperative widgets.
Do not use an effect just to derive render data from props or state:
// Avoid
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${first} ${last}`);
}, [first, last]);
// Prefer
const fullName = `${first} ${last}`;
The effect version creates an extra render and a temporary stale value. Derived values should usually be computed during render. If the calculation is expensive, consider useMemo; if it is cheap, just compute it.
A strong interview phrase: “Effects are for synchronizing with external systems. If I can calculate the value from current props and state during render, I probably do not need an effect.”
Dependency arrays: what belongs there
The dependency array should include every reactive value read by the effect: props, state, and variables or functions declared inside the component. React compares dependencies by identity using Object.is.
useEffect(() => {
document.title = `${user.name} (${unread})`;
}, [user.name, unread]);
If you omit a dependency, the effect may use stale values. If a dependency changes every render, the effect may run too often. The answer is not to lie to React; it is to stabilize the value or move logic.
Common fixes:
- Move helper functions inside the effect if they are only used there.
- Use functional state updates to remove a state dependency from a setter.
- Memoize objects or callbacks when identity stability matters.
- Store mutable, non-rendering values in refs.
Example:
useEffect(() => {
setItems(prev => mergeItems(prev, incoming));
}, [incoming]);
Because the setter uses prev, the effect does not need to read items from the closure.
Cleanup timing
An effect may return a cleanup function. React runs cleanup before the effect runs again, and when the component unmounts. In development Strict Mode, React may mount, clean up, and mount again to expose unsafe side effects.
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${id}`, { signal: controller.signal })
.then(r => r.json())
.then(setUser)
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, [id]);
The cleanup prevents a request for an old id from updating state after a newer render. It also avoids wasted work.
In interviews, avoid saying “cleanup only runs on unmount.” It also runs before re-running an effect with changed dependencies.
useMemo: cache a calculation, not a guarantee
useMemo memoizes the result of a calculation between renders until dependencies change.
const visibleRows = useMemo(() => {
return rows.filter(row => row.status === filter).sort(compareRows);
}, [rows, filter]);
Use it when:
- The calculation is meaningfully expensive.
- The memoized value is passed to a memoized child and identity stability matters.
- The value is a dependency of another hook and you need stable identity.
Do not use it for every small expression. useMemo has overhead and can make code harder to read. React may also discard memoized values in some cases; it is a performance optimization, not a semantic storage mechanism. If the program is wrong without useMemo, you probably need state or a ref instead.
A useful interview line: “I would first make the render correct, then add useMemo where profiling or child memoization shows identity churn is expensive.”
useCallback: memoize a function identity
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). It returns a stable function reference until dependencies change.
Use it when:
- Passing callbacks to
React.memochildren where identity changes cause avoidable renders. - Using a callback as a dependency of another hook.
- Registering and unregistering external listeners that depend on function identity.
Example:
const handleSave = useCallback(() => {
saveDraft({ title, body });
}, [title, body, saveDraft]);
The gotcha is stale closures. If title is omitted, handleSave may save an old title. If dependencies include a value that changes every render, the callback changes every render too and provides no stability.
Do not wrap every handler in useCallback by default. A non-memoized child will re-render when its parent renders regardless of callback identity. useCallback only helps when something actually compares identities.
Refs versus state
useRef stores a mutable .current value that persists across renders without causing a re-render. Use refs for:
- DOM nodes.
- Timer IDs.
- Latest value access from an external callback.
- Imperative handles or instance-like fields.
Use state when the UI should update in response to a change. If changing a value should be visible in the render output, it should not live only in a ref.
A common pattern for latest value:
const latestToken = useRef(token);
useEffect(() => {
latestToken.current = token;
}, [token]);
Then an external subscription callback can read latestToken.current without resubscribing on every token change. Explain this as an escape hatch, not the default.
Strict Mode gotchas
In React development Strict Mode, effects can run twice on mount: setup, cleanup, setup. This is designed to reveal effects that are not resilient to remounting. Production behavior differs, but your effect should still be idempotent and cleanup correctly.
Bad signs:
- Analytics events firing twice in development without guards.
- Subscriptions added without cleanup.
- Fetches that race and set stale state.
- Effects that mutate external state without rollback.
The fix is not “disable Strict Mode.” The fix is to make setup and cleanup symmetrical and make async work cancelable or ignorable when outdated.
Common interview questions
| Question | Strong answer | |---|---| | Why is my effect infinite-looping? | A dependency changes identity each render, or the effect updates state that changes its own dependency. Stabilize the dependency or rethink derivation. | | Can I omit a dependency? | Usually no. If including it causes issues, restructure the code so the effect does not read it or stabilize it honestly. | | useMemo vs useCallback? | useMemo caches a value; useCallback caches a function identity. Both are dependency-based performance tools. | | When should I use refs? | For mutable values that should persist but not trigger rendering, or for imperative DOM/external APIs. | | Why did an old request update my UI? | The effect lacked cancellation or an ignore flag tied to cleanup. |
Practical checklist before shipping hooks code
- Can derived data be computed during render instead of stored in state?
- Does every effect synchronize with an external system?
- Are all reactive values included in dependencies?
- Are objects and functions stable only when stability matters?
- Does cleanup undo setup and handle dependency changes?
- Could async work finish out of order?
- Is
useMemooruseCallbackjustified by expensive work or identity comparison? - Would the code still behave under Strict Mode remount checks?
How to talk about hooks on a resume
Avoid vague bullets like “used React hooks.” Show judgment:
- “Refactored effect-heavy components by moving derived state into render calculations and isolating external synchronization in
useEffect.” - “Reduced unnecessary child renders by stabilizing high-churn callback props with targeted
useCallbackandReact.memo.” - “Hardened data-fetching hooks with abortable requests and cleanup-safe dependency handling.”
- “Improved dashboard responsiveness by memoizing expensive row filtering only after profiling render bottlenecks.”
React hooks interviews reward mental models more than memorized rules. Say what each render sees, what the effect synchronizes, which identities matter, and how cleanup protects correctness. That is the difference between hook superstition and senior-level React judgment.
Custom hooks: reuse behavior, not lifecycle snippets
A custom hook should package related stateful behavior behind a clear API. Good examples include useDebouncedValue, useLocalStorage, useMediaQuery, or a data-fetching hook that owns loading, error, cancellation, and refetch behavior. The same dependency rules apply inside the hook; custom hooks do not get a special exemption from stale closures.
A strong custom hook has a small contract:
const { data, error, isLoading, refetch } = useUserProfile(userId);
The caller should not need to know whether the hook uses effects, refs, memoization, or an AbortController internally. That separation is why hooks are useful: they share behavior without hiding render output inside inheritance or mixins.
In interviews, explain custom hooks as composition. “I would extract this when multiple components need the same subscription or async state machine, but I would keep presentation-specific derived values in the component.” That answer shows you are not extracting just to make files smaller.
Server rendering and effects
Effects do not run during server rendering. If a value depends on window, layout measurement, or browser storage, the initial server render cannot know it. That can cause hydration mismatches if you read browser-only values during render. The usual fix is to render a safe initial state, then synchronize in an effect on the client, or use framework-specific client-only boundaries. For senior React interviews, this distinction matters because hooks are often discussed in the context of modern SSR frameworks.
Related guides
- CAP Theorem Interview Deep-Dive: What to Actually Say When Asked CP vs AP — CAP is the most misused three letters in system design interviews. Here's the precise answer, the PACELC correction most candidates miss, and what to say when asked to pick CP or AP.
- Deep Learning Interview Questions in 2026 — Backprop, Optimizers, and Regularization — A 2026-ready deep learning interview guide covering backpropagation, optimizers, regularization, debugging, transformers, evaluation, and sample answers that show practical judgment.
- React Component Design Interview — Composition, State Lifting, and Rendering Optimization — A practical guide to React component design interviews: how to structure components, decide where state lives, avoid render traps, and explain tradeoffs clearly under interview pressure.
- React Interview Cheatsheet in 2026 — Patterns, Examples, Practice Plan, and Common Traps — A practical React interview cheatsheet for 2026 covering component design, hooks, state ownership, rendering performance, accessibility, examples, traps, and a prep plan.
- A/B Testing Interview Questions in 2026 — Power Analysis, Peeking, and SRM — A tactical guide to A/B testing interview questions in 2026, with answer frameworks for power analysis, peeking, sample-ratio mismatch, guardrails, metrics, and experiment trade-offs. Built for product analysts, data scientists, PMs, and growth roles.
