Detailed explanation
useEffect and useLayoutEffect both let a React component synchronise with something outside rendering, such as browser APIs, subscriptions, or DOM measurements. Their main difference is when React executes their setup functions relative to a committed DOM update and the browser’s next paint. Neither runs during rendering. Prefer useEffect by default; choose useLayoutEffect when the user must not see an intermediate visual state, such as a tooltip positioned incorrectly before its dimensions are known.
During the commit phase, React applies DOM changes and makes DOM refs available. It then runs useLayoutEffect before the browser paints those changes. You can read an element’s dimensions using ref.current.getBoundingClientRect() and update state with the measurement. React processes that state update before allowing the browser to repaint, so the user sees the corrected layout rather than the initial attempt. In the given example, setHeight(h) can trigger another render using the measured height. This prevents flicker, but the measurement, effect code, and resulting update delay paint; expensive work here directly hurts responsiveness.
useEffect is a passive effect: React generally lets the browser paint first, particularly for updates not caused by user interactions. It is suitable for fetching data, managing subscriptions, and synchronising with external systems when pre-paint timing is unnecessary. However, saying it always runs asynchronously after paint or never blocks visual updates is inaccurate. React may run an interaction-triggered effect before paint, and a state update inside useLayoutEffect can cause pending effects to run immediately. Also, effect callbacks execute JavaScript on the main thread, so expensive work can still delay subsequent frames or interactions.
Both hooks accept dependency arrays and support cleanup: when dependencies change, the previous cleanup runs before the next setup, and cleanup also runs on unmount. Neither executes during server rendering, where browser layout is unavailable. In development Strict Mode, React performs an extra setup-and-cleanup cycle to expose missing cleanup. Interviewers usually expect you to explain the paint-order distinction, identify a genuine layout-sensitive use case, and justify why useEffect remains the safer default.
Key points to remember
- Both hooks run after React commits DOM changes, not during rendering.
- useLayoutEffect runs before paint and blocks repaint until its work and resulting state updates finish.
- useEffect generally runs after paint, but this timing is not guaranteed.
- Use useLayoutEffect for measurement-dependent visual corrections; prefer useEffect for most external synchronisation.
- Both support dependencies and cleanup, and neither runs during server rendering.
Common mistakes candidates make
- Claiming useEffect always runs after paint or executes its callback on a background thread.
- Replacing every useEffect with useLayoutEffect, unnecessarily delaying paint.
- Assuming an empty dependency array keeps a measurement current when content, fonts, or container dimensions change.
Likely follow-up questions
Why can measuring an element in useEffect cause flicker?
The browser may paint the initial layout before the effect measures the element and updates state. useLayoutEffect lets React apply the measurement-dependent correction before that paint.
How should you handle an element whose size changes after mounting?
An effect with an empty dependency array does not automatically rerun when an element resizes. Use ResizeObserver to track element-size changes and disconnect it during cleanup; avoid updates that create resize feedback loops.
What happens to useLayoutEffect during server-side rendering?
It does not run on the server, because there is no browser DOM to measure. If server-rendered markup cannot look correct without measurement, consider a stable fallback until the client mounts rather than assuming the hook can correct HTML before hydration.
Interview tip: Explain the commit-to-paint sequence, illustrate it with a tooltip measurement, and explicitly qualify the claim that useEffect always runs after paint.
