GAP·MAP
← all breakdowns
ReactEffectsClosuresDependenciesmiddle level

How useEffect Really Behaves

the question

Walk me through how `useEffect` behaves. When does it run, how do dependencies and cleanup work, what causes stale closures, and when would you avoid an Effect?

what they're testing: The interviewer is testing whether you can reason about synchronization across renders and avoid the lifecycle, dependency, and closure mistakes that cause real React bugs.

a strong answer

useEffect is for keeping a component in sync with something outside React, such as a subscription, timer, browser API, or network connection. React runs the setup after the component commits. It runs after the initial commit, then runs again only when its dependencies require it.

With no dependency array, the Effect runs after every commit. With [], prop and state changes do not make it re-run during that mount. With [roomId], React compares roomId with its previous value using Object.is. When it changes, cleanup runs first with the old captured values, then the new setup runs. Cleanup also runs on unmount. In development Strict Mode, React adds a setup, cleanup, setup cycle to expose broken cleanup.

Dependencies are not timing controls. Every reactive value used by the Effect belongs in the array. Each render creates a closure over that render's props and state. If a dependency is omitted, a timer or listener can keep using an old closure and see stale values.

Before adding an Effect, ask what external system needs synchronization. Derived values belong in render, expensive calculations may use useMemo, and user-triggered work usually belongs in its event handler. Without an external system, the Effect is usually unnecessary.

Why it lands

  • Frames an Effect as synchronization rather than a lifecycle callback.
  • Explains dependency comparison and the cleanup-before-setup sequence.
  • Connects stale closures to the values captured by a particular render.
  • Shows when render logic or an event handler is the simpler choice.

Where people slip

  • `[]` guarantees the Effect runs exactly once for the lifetime of the app.
  • Cleanup runs only when the component unmounts.
  • If an Effect runs too often, remove the changing dependency or suppress the linter.
  • Derived values should be calculated in an Effect and copied into state.

If they push further

Why can an object or function dependency make an Effect run repeatedly?

Objects and functions created during rendering are usually new references, so `Object.is` reports a change. Move non-reactive construction outside the component or inside the Effect; use `useMemo` or `useCallback` only when stable identity is actually needed.

How would you fix an interval that always sees the initial state?

Use a functional state updater if the interval only needs the previous value. If the changed value should reconfigure the interval, include it as a dependency and let cleanup replace the old interval.

When would you choose `useLayoutEffect` instead?

Use it when layout measurement and the resulting update must finish before the browser paints, such as positioning a tooltip without flicker. Prefer `useEffect` when blocking paint is unnecessary.

Sources

Now answer it yourself.

Reading a strong answer is easy. Producing one under pressure is the skill the interview tests. Gapmap grades your answer against the same bar an interviewer would.

was this useful?

beta

The interviewer part is in the works.

The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.