Implement a polyfill for useLayoutEffect
useLayoutEffect runs synchronously after DOM mutations but before the browser paints — unlike useEffect which runs after paint. A 'polyfill' isn't a userland thing (it needs the commit phase); the real answer is explaining the timing and that on the server it must fall back to useEffect to avoid warnings.
This is a bit of a trick question — useLayoutEffect can't be truly polyfilled in userland, because its defining feature is when React's renderer runs it (the commit phase, synchronously, before paint). The interview is really testing whether you understand that timing.
What useLayoutEffect actually does
useEffect | useLayoutEffect | |
|---|---|---|
| When | after the browser paints | after DOM mutations, before paint |
| Blocking | non-blocking, async | blocks paint — synchronous |
| Use for | most side effects (data, subscriptions) | DOM measurement + mutation the user must not see flicker |
The point: if you measure the DOM and then change layout based on it, doing that in useEffect causes a visible flicker — the user sees the wrong layout for one frame. useLayoutEffect runs before paint, so the correction is invisible.
The closest "polyfill" — the isomorphic pattern
The one real-world thing people call a useLayoutEffect polyfill: useLayoutEffect warns when run on the server (there's no layout to lay out during SSR). The standard fix is an isomorphic effect that picks the right hook:
const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect;On the client you get real layout-effect timing; on the server it falls back to useEffect (which no-ops during SSR anyway), silencing the warning. Most libraries (Redux, Floating UI) ship exactly this.
Why you can't build it from scratch
To get "synchronously after DOM mutation, before paint," you'd need a hook into React's commit phase — that's internal renderer scheduling, not something useEffect + requestAnimationFrame can replicate. useEffect + useRef can approximate "run after render" but never "before the browser paints."
The framing
"It's a trick — useLayoutEffect can't be polyfilled in userland because its whole identity is renderer timing: it runs synchronously after DOM mutations but before paint, where useEffect runs after paint. That difference is what prevents flicker when you measure-then-mutate the DOM. The thing people call a polyfill is the isomorphic effect: typeof window !== 'undefined' ? useLayoutEffect : useEffect, which falls back to useEffect on the server to avoid the SSR warning."
Follow-up questions
- •Why does useLayoutEffect warn during SSR?
- •When would using useLayoutEffect cause a flicker that useEffect doesn't?
- •Why can't useEffect + requestAnimationFrame replicate it?
- •What's the performance cost of useLayoutEffect?
Common mistakes
- •Claiming you can fully polyfill it with useEffect — you can't replicate the timing.
- •Not knowing the SSR warning and the isomorphic-effect fix.
- •Thinking useLayoutEffect and useEffect are interchangeable.
- •Using useLayoutEffect everywhere — it blocks paint.
Performance considerations
- •useLayoutEffect blocks the browser from painting until it finishes — keep it light and use it only for synchronous DOM measure/mutate. For everything else, useEffect avoids blocking paint.
Edge cases
- •SSR — useLayoutEffect doesn't run and warns.
- •Heavy work in useLayoutEffect delays paint, hurting perceived performance.
- •Concurrent rendering interactions.
Real-world examples
- •Measuring an element and positioning a tooltip/popover before paint to avoid a jump.
- •Redux and popover libraries shipping useIsomorphicLayoutEffect.