Volume 3

Q155: Effects, Synchronization and Dependency Arrays

Difficulty: SeniorFrequency: HighAnswer time: 10-14 minutes

What Interviewers Want To Evaluate

This question checks whether you understand effects as synchronization, not lifecycle nostalgia. Interviewers want dependency arrays, cleanup, stale closures, subscriptions, fetching, event listeners, strict mode double-invocation in development, and when an effect is unnecessary.

Senior answers should show restraint: many effects should be removed, derived, or moved to event handlers.

Short Interview Answer

React effects synchronize a component with systems outside React, such as subscriptions, browser APIs, timers, imperative widgets, and network requests. The dependency array tells React which reactive values the effect uses, so React can rerun the effect when those values change. Cleanup removes or cancels the previous synchronization. Effects should not be used for simple derived state or event-specific logic.

Detailed Interview Answer

An effect runs after React commits UI updates.

useEffect(() => {
  document.title = title;
}, [title]);

This synchronizes the document title with React state.

Effects Are Synchronization

Good effect use cases include:

subscribe to external store
attach browser event listener
start and clear timer
connect to websocket
control imperative widget
sync document title
fetch data when route param changes

The key phrase is external system.

Dependency Array

The dependency array should include reactive values used by the effect.

useEffect(() => {
  chat.connect(roomId);

  return () => {
    chat.disconnect(roomId);
  };
}, [roomId]);

When roomId changes, React cleans up the old connection and creates the new one.

Cleanup

Cleanup prevents leaks and stale work.

useEffect(() => {
  const controller = new AbortController();

  loadUser(userId, controller.signal);

  return () => {
    controller.abort();
  };
}, [userId]);

This cancels obsolete request work.

Stale Closures

Effects close over values from the render that created them.

If a dependency is missing, the effect may keep using stale values.

This is why dependency linting matters.

Unnecessary Effects

Do not use effects for values that can be derived during render.

const fullName = `${firstName} ${lastName}`;

This does not need:

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

Derived state in effects creates extra renders and bugs.

Event Logic

Event-specific logic belongs in the event handler.

function handleSubmit() {
  submitForm(values);
}

Do not create an effect that watches shouldSubmit unless the action is truly caused by rendering state.

Strict Mode Development Behavior

In development, React Strict Mode may run effects more than once to reveal unsafe synchronization and missing cleanup.

The answer is not to disable Strict Mode. The answer is to make effects resilient and clean up properly.

Fetching in Effects

Fetching in effects is common, but it needs cancellation or stale-result protection.

useEffect(() => {
  let ignore = false;

  fetchUser(userId).then((user) => {
    if (!ignore) {
      setUser(user);
    }
  });

  return () => {
    ignore = true;
  };
}, [userId]);

Framework loaders or server components may be better for route-level data.

Common Mistakes

A common mistake is suppressing dependency lint rules without understanding the stale closure being created.

Another mistake is putting derived state in effects.

Another mistake is forgetting cleanup for subscriptions, timers, and requests.

Senior Trade-Offs

Effects are powerful but easy to overuse.

Before writing an effect, ask:

am I synchronizing with an external system?
can this be derived during render?
does this belong in an event handler?
what is the cleanup?
what values does the effect read?
can this become stale?

Code or Design Example

useEffect(() => {
  function onKeyDown(event) {
    if (event.key === "Escape") {
      closeModal();
    }
  }

  window.addEventListener("keydown", onKeyDown);

  return () => {
    window.removeEventListener("keydown", onKeyDown);
  };
}, [closeModal]);

This effect synchronizes the modal with a browser event listener.

Debugging Workflow

When effects misbehave:

check whether effect is needed
check missing dependencies
check unstable function or object dependencies
check cleanup behavior
check stale async results
check Strict Mode development behavior
move event logic to handlers
derive values during render where possible

Interview Framing

Define effects as synchronization with external systems. Then explain dependency arrays, cleanup, stale closures, and unnecessary effects.

Revision Notes

Effects are not general-purpose reaction code. They synchronize committed UI with outside systems.

Key Takeaways

Senior React code often improves by deleting effects, not adding more of them.

Follow-Up Questions

  1. When does useEffect run?
  2. What is an external system?
  3. What should go in the dependency array?
  4. Why is cleanup important?
  5. What is a stale closure?
  6. Why is derived state in effects risky?
  7. When should logic stay in an event handler?
  8. Why does Strict Mode rerun effects in development?
  9. How do you handle fetch races?
  10. How do you decide if an effect is unnecessary?

How I Would Answer This In A Real Interview

I would say effects synchronize React with external systems after commit. I would include all reactive dependencies, clean up subscriptions and requests, avoid derived state in effects, and move event-specific logic into event handlers.