All topics
Performanceadvanced

Reflow, Repaint, and Layout Thrashing

The two expensive rendering steps triggered by DOM/style changes, and the anti-pattern of alternating reads and writes that forces the browser to recalculate layout repeatedly.

Reflow (also called layout) and repaint are two distinct, potentially expensive steps the browser performs after a DOM or style change, and understanding when each is triggered — and how to avoid triggering them excessively — is central to writing performant DOM-manipulation code, especially for animations or frequently-updating UI.

Layout thrashing is like repeatedly asking a mover 'how much does the truck weigh right now?' immediately after loading each individual box, forcing them to stop and put the truck on a scale after every single box, instead of letting them load the entire truck first and weigh it just once at the very end.

Key Concepts

1
Reflow is the browser recalculating the geometry (position and size) of elements affected by a change — adding or removing an element, changing dimensions, or reading certain layout-dependent properties can all trigger it, and because layout is a genuinely hierarchical calculation, a reflow on one element can cascade into needing to recalculate large portions of the page. Repaint is a comparatively cheaper step where the browser redraws pixels to reflect visual changes (like a color or visibility change) that don't affect layout geometry at all — repaint-only changes are less expensive than ones that also require a full reflow.
2
The specific performance anti-pattern called 'layout thrashing' happens when code alternates between *writing* to the DOM (which invalidates the browser's cached layout) and then *reading* a layout-dependent property (like element.offsetHeight or getBoundingClientRect()), forcing the browser to synchronously recompute layout immediately, on the spot, right in the middle of your script, rather than being able to batch and defer that recalculation until the next natural rendering opportunity. Doing this repeatedly in a loop — write, read, write, read — forces a full synchronous reflow on every single iteration, which can be dramatically slower than doing all the reads first, then all the writes, in two separate batched passes.
element.offsetHeightgetBoundingClientRect()
3
The fix is straightforward once you know to look for it: batch all your DOM reads together first, store whatever values you need in local variables, and only then perform all your DOM writes together in a second pass — this lets the browser's normal layout scheduling batch and defer the actual recalculation naturally, instead of being forced to do it synchronously and repeatedly inside your loop. Modern frameworks that use a virtual DOM (covered elsewhere) largely handle this batching automatically underneath their declarative API, which is part of why hand-rolled direct DOM manipulation code needs more manual discipline to avoid this specific class of performance bug.