Explain why React StrictMode intentionally double-invokes certain lifecycles and effects in development, how this simulates mount–unmount–remount, and what kinds of side-effect and cleanup bugs it is designed to reveal.
Why does StrictMode double-invoke effects in dev? What bugs does it expose?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Short answer
React StrictMode double-invokes effects in development to stress-test your side effects. It intentionally simulates mount → unmount → mount to expose bugs where effects are not idempotent or don’t clean up properly.
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
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.