Explain the timing difference between useEffect and useLayoutEffect in the render/commit/paint pipeline, why useLayoutEffect blocks painting, and when it is required (DOM measurements, preventing visual flicker) versus when useEffect is preferred.
What’s the difference between useEffect and useLayoutEffect? When does it matter?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Short answeruseEffect runs after the browser paints. useLayoutEffect runs synchronously after DOM mutations but before the browser paints. The difference matters when you need to read or write layout and avoid visual flicker.
The render pipeline (mental model)
Roughly, React does this:
1) Render (compute virtual tree)
2) Commit (apply DOM changes)
3) useLayoutEffect runs here (before paint)
4) Browser paints to screen
5) useEffect runs here (after paint)
Hook | When it runs | Does it block paint? |
|---|---|---|
useLayoutEffect | After DOM updates, before paint | ✅ Yes (blocks paint) |
useEffect | After paint | ❌ No (non-blocking) |
Why this difference existsuseLayoutEffect exists for code that must synchronously read or modify the DOM before the user sees anything. useEffect is for everything else (data fetching, subscriptions, logging, timers, etc.).
function Example() {
const ref = useRef(null);
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
// Measure and synchronously adjust layout
}, []);
return <div ref={ref}>Hello</div>;
}
What happens if you use useEffect for layout work?
The browser will:
• Paint the UI once
• Then your effect runs
• Then you update layout/state
• Then it paints again
The user sees a visual flicker / jump.
function Tooltip({ text }) {
const ref = useRef(null);
const [top, setTop] = useState(0);
// ❌ This can cause visible jump
useEffect(() => {
const rect = ref.current.getBoundingClientRect();
setTop(rect.top - 10);
}, []);
return (
<div ref={ref} style={{ position: 'absolute', top }}>
{text}
</div>
);
}
Correct version:
useLayoutEffect(() => {
const rect = ref.current.getBoundingClientRect();
setTop(rect.top - 10);
}, []);
// ✅ User never sees the wrong position
When you should use useLayoutEffect
Scenario | Why | Examples |
|---|---|---|
Measuring DOM size/position | Must read layout before paint | getBoundingClientRect, offsetWidth |
Synchronously adjusting layout | Prevent visible jumps | Tooltips, popovers, modals |
Scroll position corrections | Avoid one-frame wrong scroll | Scroll restoration, anchored lists |
Imperative animations setup | Need correct initial layout | FLIP animations, measurement-based motion |
When you should use useEffect (almost always)
• Data fetching
• Subscriptions / event listeners
• Logging / analytics
• Timers
• Syncing with external systems
These do not need to block paint and should not slow down rendering.
Why you should avoid overusing useLayoutEffect
Because it blocks the browser from painting. Too many or heavy useLayoutEffect calls = slow first paint and janky UI.
SSR warninguseLayoutEffect does nothing on the server and causes warnings in SSR environments. Many frameworks alias it to useEffect on the server. Another reason to only use it when truly necessary.
Rule of thumb
Start with useEffect. Only switch to useLayoutEffect if you see a visual flicker, layout jump, or need to measure/mutate layout before paint.
Interview framing
Say it like this:
"useLayoutEffect runs synchronously after DOM mutations but before paint and blocks rendering, so it’s for layout measurement and visual correctness. useEffect runs after paint and is non-blocking, so it’s for side effects like data fetching and subscriptions. You should prefer useEffect unless you need to prevent visual flicker."
useLayoutEffect runs before the browser paints and blocks rendering, making it suitable for DOM measurements and layout corrections. useEffect runs after paint and should be used for most side effects. Overusing useLayoutEffect hurts performance, so only use it when visual correctness requires it.