Explain StrictMode double effect invocation as a dev-only diagnostic for missing cleanup, duplicate side effects, and code that is not safe under remounts or concurrent-style replay.
Use this React interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
Why does StrictMode double-invoke effects in dev? What bugs does it expose?Frontend interview answer
This React interview question tests whether you can explain React StrictMode double effects: cleanup bugs, duplicate side effects, and dev-only diagnostics, connect it to production trade-offs, and handle common follow-up questions.
- React StrictMode double effects: cleanup bugs, duplicate side effects, and dev-only diagnostics explanation without falling back to memorized docs wording
- Effects and Best Practices reasoning, edge cases, and production failure modes
- How you would answer the most likely React interview follow-up
Debug diagnostic
StrictMode double-invokes effects in development to flush out bugs that stay hidden when code assumes “this only runs once.” The practical value is finding missing cleanup, duplicate subscriptions, repeated network calls, and non-idempotent side effects before those same assumptions break under remounts, navigation churn, or concurrent-style replay.
Important: this only happens in development
StrictMode checks run only in dev. Production builds do not double-invoke effects. This is a diagnostic tool, not a performance feature.
The mental model
React is preparing for a future where it can pause, discard, and restart renders (concurrent rendering). To make sure your code is safe for that world, StrictMode asks a brutal question:
"If I mount this component, tear it down, and mount it again immediately… does anything break?"
function Example() {
useEffect(() => {
console.log('effect run');
return () => {
console.log('cleanup');
};
}, []);
return <div>Hello</div>;
}
// In StrictMode (dev), you'll see:
// effect run
// cleanup
// effect run
What exactly gets double-invoked?
In React 18 StrictMode (dev):
• Component render functions
• useEffect / useLayoutEffect setup + cleanup
• Certain initializers
React mounts, cleans up, and mounts again immediately.
What React is testing | Bad pattern | Bug it exposes |
|---|---|---|
Missing cleanup | Subscribing but never unsubscribing | Memory leaks, duplicate listeners |
Non-idempotent side effects | Sending analytics / POST request on mount | Duplicate network calls or double charges |
Mutating external state | Pushing into global arrays, singletons | Duplicated or corrupted global state |
Assuming effects run only once | “This runs once on mount” logic | Breaks under remounts / transitions |
Classic bug example: missing cleanup
useEffect(() => {
window.addEventListener('resize', onResize);
// ❌ No cleanup
}, []);
// In StrictMode dev, this registers the listener twice 😬
Correct version:
useEffect(() => {
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, []);
// ✅ Safe under mount → unmount → mount
Another classic: non-idempotent side effects
useEffect(() => {
analytics.track('page_view'); // ❌ will fire twice in dev StrictMode
}, []);
Better patterns:
• Move it to a router-level boundary
• Or make it idempotent
• Or dedupe on the analytics side
Worked fetch example: duplicate request vs cleanup-safe request
The common complaint is “StrictMode fetched twice.” The real question is whether the effect is safe when React remounts the component or the user changes routes quickly.
// ❌ Duplicate fetch and late stale response can win after remount/navigation
useEffect(() => {
fetch(`/api/profile/${userId}`)
.then((r) => r.json())
.then(setUser);
}, [userId]);
// ✅ Abort the old request when StrictMode remounts or the route changes
useEffect(() => {
const controller = new AbortController();
fetch(`/api/profile/${userId}`, { signal: controller.signal })
.then((r) => r.json())
.then((data) => {
if (!controller.signal.aborted) setUser(data);
})
.catch((error) => {
if (controller.signal.aborted) return;
setError(error);
});
return () => controller.abort();
}, [userId]);
Why the route-switch scenario matters
Dev-only replay maps to real user behavior. A user can open a profile, navigate away immediately, and come back. If your effect leaks subscriptions or lets an old request commit after unmount, that is a production bug even though StrictMode is what exposed it first.
Extra render checks vs extra effect re-runs
StrictMode stresses more than one surface. Extra render calls catch impure render logic. Extra effect setup/cleanup cycles catch missing cleanup and non-idempotent side effects. Keep those two diagnostics separate when debugging double logs.
Why React does this (the real reason)
Concurrent React may mount, pause, throw away, and restart trees. If your effects are not resilient to being started and stopped multiple times, you’ll get production-only bugs. StrictMode forces you to see them early.
What this is NOT
• Not a bug
• Not React being slow
• Not something to “work around”
It’s a correctness check.
Rule of thumb
Write effects as if React can run them, clean them up, and run them again at any time. If that is safe, your code is future-proof.
Interview framing
Say it like this:
"StrictMode double-invokes effects in dev to simulate mount–unmount–remount and catch unsafe side effects. It exposes missing cleanups, non-idempotent logic, and code that assumes effects run only once. This is to prepare apps for concurrent rendering and only runs in development."
StrictMode intentionally double-invokes effects in development to detect unsafe side effects and missing cleanup. It helps catch memory leaks, duplicated subscriptions, non-idempotent logic, and code that assumes a single mount. This behavior is dev-only and exists to make apps safe for concurrent rendering.
Use this as one explanation rep, then continue with the React interview questions cluster or a guided prep path.