Short answer
useEffect runs after React commits a render. The dependency array tells React when to re-run it: with no array it runs after every render, with an empty array it runs once after mount, and with values it re-runs whenever one of them changes by Object.is comparison. The optional cleanup function runs before the next effect and on unmount.
Typical pattern
tsxuseEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((r) => r.json())
.then(setUser);
return () => controller.abort(); // cleanup
}, [userId]);Common mistakes
- Missing dependencies, which leave the effect reading stale values.
- Objects or functions created during render as dependencies, which change every render.
- Using an effect to derive state that could be computed during render.
- Forgetting cleanup for subscriptions, timers and in-flight requests.
How to answer it in an interview
- Say that effects are for synchronising with external systems, not for data flow inside React.
- Mention that Strict Mode runs effects twice in development to surface missing cleanup.