Layout Thrashing
Layout thrashing is the read-write-read cycle where JavaScript reads layout geometry, mutates styles, and forces the browser to synchronously recompute layout dozens of times per frame.
Definition
Layout thrashing (forced synchronous layout) occurs when code interleaves layout-reading properties like offsetHeight or getBoundingClientRect with style writes. Each read after a write forces layout computation synchronously before the frame, so the browser cannot batch, and cost multiplies to frame-budget overruns and jank.
Context and scope
Appears in drag-and-drop handlers, parallax scroll effects, virtualized list positioning, and any animation loop that queries geometry then writes styles per element in a loop.
Common misconceptions
- Only heavy pages thrash — a simple list thrashes if reads and writes alternate per element in a loop
- Reading layout is free — reads force layout when pending changes exist; the first read after a write pays full cost
- Transforms cause layout — transform and opacity are compositor-friendly and skip layout entirely
Anti-patterns
- Reading offsetTop inside the same loop iteration that writes element styles, forcing layout per element
- Running layout-reading code in scroll or mousemove handlers at event frequency instead of frame frequency
- Querying geometry for hundreds of nodes then writing one style each, interleaved rather than batched
Related concepts
Related examples
Authoritative source
https://developer.mozilla.org/en-US/docs/Glossary/Layout_thrashing
What it is
The browser wants to run layout once per frame, after all style writes. Interleaved reads defeat this: a read of layout-dependent geometry after pending writes forces the layout to run immediately and synchronously on the main thread. Loop that pattern over many elements and layout runs that many times.
How it works
Style writes mark the document dirty; the next layout query — offsetWidth, getBoundingClientRect, computed style of geometry-affecting properties — flushes the dirty state. The fix is phase separation: batch every read first into cached values, then apply all writes. rAF callbacks schedule the frame coherently.
When to fix
Long Animation Frames entries and forced-reflow warnings in performance traces pinpoint the offenders. Drag interactions, spreadsheet-like UIs, and chart animations are the usual hosts. Moving pure animation to transform and opacity offloads work to the compositor and sidesteps layout altogether.
Pitfalls
Hidden readers lurk in libraries: calling getBoundingClientRect inside a scroll listener per event can force layout more often than frames exist. Caching geometry between frames trades staleness for speed; recompute on resize through ResizeObserver instead of reading inline.