useEffect vs useLayoutEffect
`useEffect` runs after the browser paints — async, doesn't block visual updates. `useLayoutEffect` runs synchronously after the DOM mutates but BEFORE paint — use it when you need to measure layout and mutate the DOM before the user sees anything. Default to `useEffect`; reach for `useLayoutEffect` only for measure-and-adjust patterns (tooltips, animations from a measured position).
Same API, different scheduling.
render → commit (DOM updates) → useLayoutEffect (sync, blocks paint) → paint → useEffect (async)When to use each
useEffect (the default).
- Side effects that don't need to block rendering: fetches, subscriptions, logging, timers.
- Runs after paint, so the user sees the new UI as fast as possible.
- Cleanup runs before the next effect or on unmount.
useLayoutEffect.
- You need to read layout (height, width, scroll position) and mutate the DOM before the user sees a flash of the wrong state.
- Examples:
- Position a tooltip relative to a trigger button.
- Measure a list item's height and use it for parent layout.
- Synchronously focus an element after mount where useEffect would cause a visible flicker.
- Custom scroll restoration where you need to set
scrollTopbefore paint.
function Tooltip({ targetRef, children }) {
const ref = useRef<HTMLDivElement>(null);
const [pos, setPos] = useState({ top: 0, left: 0 });
useLayoutEffect(() => {
const r = targetRef.current!.getBoundingClientRect();
setPos({ top: r.bottom + 4, left: r.left });
}, [targetRef]);
return <div ref={ref} style={pos}>{children}</div>;
}If you used useEffect here, the user would see the tooltip briefly at the wrong position (0,0) before it jumped to the right place.
The trade-off
useLayoutEffect runs synchronously and blocks the browser from painting. Heavy work inside it makes interactions feel laggy. Use it sparingly — only when not using it produces a visible artifact.
The general rule: start with useEffect. If you see a flicker, reach for useLayoutEffect.
SSR
useLayoutEffect doesn't run on the server. React emits a warning when it runs in SSR contexts ("useLayoutEffect does nothing on the server"). For libraries that need to support both:
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;(useInsertionEffect runs even earlier — used by CSS-in-JS libraries to inject styles before any layout effect reads them. Don't reach for it unless you're building a CSS-in-JS lib.)
Common pitfalls
- Heavy work in useLayoutEffect. Blocks paint. If you're doing more than DOM measurement + setState, it probably belongs in useEffect.
- Fetching in useLayoutEffect. Never. Fetches are async; the layout effect can't await them anyway, and the synchronous part you do execute blocks paint.
- Cascading layout effects. Each layout effect that triggers a setState causes another synchronous render. Two or three nested can compound into noticeable jank.
- Reading layout in render.
elem.getBoundingClientRect()during render returns stale values (DOM not committed yet). Read in an effect, set state, re-render with the measurement.
Order of effects
Within a tree:
- All
useLayoutEffects in children → then in parents (bubble up). - Then the same order for
useEffects, but async after paint.
If a child measures and a parent layout effect depends on it: the child runs first; the parent's read-after sees the updated DOM (within the same commit) and child setState's render is part of the same paint.
Senior framing
The interviewer wants:
- Knowing the scheduling difference — paint boundary.
- A specific use case for
useLayoutEffect(tooltip, measure-and-adjust). - Default to useEffect discipline.
- SSR awareness — useLayoutEffect doesn't run on the server.
- Cost awareness — useLayoutEffect blocks paint.
The "useLayoutEffect runs before paint" answer is mid. Naming the measure-and-adjust pattern + cost + SSR caveat is senior.
Follow-up questions
- •What is useInsertionEffect and why does it exist?
- •Why doesn't useLayoutEffect run on the server?
- •When does useEffect cause a visible flicker that useLayoutEffect fixes?
- •How do nested layout effects compose?
Common mistakes
- •Reaching for useLayoutEffect everywhere 'to be safe' — kills perf.
- •Doing async work in useLayoutEffect.
- •Reading layout in render instead of in an effect.
- •Forgetting cleanup — both hooks need it for subscriptions/timers.
Performance considerations
- •useLayoutEffect blocks paint — keep it minimal.
- •useEffect schedules work after paint — safer default.
Edge cases
- •SSR: useLayoutEffect warns; use the isomorphic alternative.
- •Strict Mode double-runs effects (incl. layout) — keep them idempotent.
- •Nested layout effects can produce visible mid-paint state if they trigger more renders.
Real-world examples
- •Popper.js / Floating UI tooltips — use layout effects to position before paint.
- •Headless UI's Listbox / Menu — measure-and-adjust on open.