Avoiding Layout Thrashing
Stop forced sync layout loops: batch DOM reads and writes, use rAF, and fix janky measurement code.
- performance
- layout
- reflow
- thrashing
- rendering
Layout thrashing (forced synchronous layout in a loop) is when JavaScript alternates DOM writes and geometry reads so the browser must recompute layout on every iteration. Profiles show a zipper of Layout events; users feel scroll and input jank.
Docs: Avoid layout thrashing — web.dev, What forces layout.
The anti-pattern
// BAD: write → read → write → read
items.forEach((el) => {
el.classList.add('open'); // write (dirty style)
const h = el.offsetHeight; // read → forced layout
el.style.height = h + 10 + 'px'; // write
});
Each offsetHeight forces the engine to flush pending style/layout for correctness.
Geometry reads that flush
offsetWidth/Height, clientWidth/Height, scrollWidth/Height, getBoundingClientRect(), getComputedStyle() (often), scrollTop writes/reads combos — treat them as flush points.
Batch reads, then writes
// GOOD
const heights = items.map((el) => el.offsetHeight); // all reads
items.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'; // all writes
});
Or measure once per frame:
requestAnimationFrame(() => {
const top = el.getBoundingClientRect().top;
requestAnimationFrame(() => {
el.style.transform = `translateY(${-top}px)`;
});
});
Double-rAF patterns schedule writes in the next frame after reads — use sparingly and document why.
Prefer transform over layout
/* Avoid animating height for open/close when possible */
.panel {
transform: scaleY(1);
transform-origin: top;
}
When height animation is a product requirement, accept layout cost or use FLIP techniques carefully.
Libraries and frameworks
- Reading layout in React
useLayoutEffectis intentional but expensive if huge. - Virtual lists reduce how many nodes exist to thrash — virtual lists.
- Measure tools (tooltip placement) should cache rects per frame, not per mouse move event without rAF throttle.
let scheduled = false;
window.addEventListener('scroll', () => {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
scheduled = false;
updateSticky(header.getBoundingClientRect());
});
}, { passive: true });
How to confirm
Performance panel: interleaved Recalculate Style → Layout inside a script loop. Fix until layout events collapse into fewer flushes. See Reflow vs repaint.
Interview out-loud
“Thrashing is alternating DOM writes and geometry reads so layout runs every iteration. I batch reads then writes, throttle measurements to rAF, and prefer transform animations. Performance panel shows the forced layout zipper.”
How this shows up in interviews
Be ready to define the metric or technique in one sentence, name one measurement approach (DevTools panel, web-vitals, or headers), and cite a concrete fix you would try first. Walk through a before/after: what the waterfall or flame chart showed, what you changed, and which percentile moved. Mention a tradeoff (complexity, caching correctness, or third-party business constraints) so the answer doesn’t sound like a blog checklist.
Production guardrails
Ship behind a flag when the change is risky, watch field p75 for the affected template for at least a few days, and keep a rollback path. Pair lab verification (throttled Performance/Network) with RUM so you don’t celebrate a Lighthouse-only win. Document the owner of any ongoing budget or third-party exception.
Related
Further depth
Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.
Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.
Further reading
Related guides
- Caching Static Assets FingerprintingContent-hash filenames, long-cache headers, HTML revalidation, and CDN invalidation without stuck users.
- CLS Optimization TacticsFix cumulative layout shift: dimensions, font metrics, reserved slots, and stable late-loading UI.
- Code Splitting StrategiesRoute, component, and vendor splits: dynamic import(), magic comments, and avoiding over-splitting waterfalls.
- Compression gzip brotliEnable Brotli/gzip for text assets, pick quality levels, and verify Content-Encoding in production.
- Critical CSS ApproachesInline above-the-fold CSS, extract critical rules, and avoid shipping megabyte stylesheets on first paint.