Under The Hood
frontend browser Chromium/WebKit layout & paint model

Layout Reflow vs Repaint: Forced Synchronous Layouts & Geometry Reads

Last updated
Prerequisites:
CSS layout and the box model
Understanding of the rendering pipeline
  • browser
  • reflow
  • repaint
  • layout
  • forced-layout
  • performance
  • geometry

Read at your depth

The practical view

Reflow (layout) recomputes the geometry (position/size) of elements; repaint redraws the pixels. Reading layout properties like offsetWidth, getBoundingClientRect(), offsetTop forces a synchronous reflow if the layout is dirty — the browser cannot answer without computing layout first. Writing styles (changing width/height) marks layout dirty but batches the actual reflow until needed. The classic perf bug is a read-write-read-write loop (layout thrashing): each read forces a reflow, making the loop O(N) reflows. Fix: batch reads together and writes together, or use modern APIs (IntersectionObserver, ResizeObserver).

The same idea in other frameworks

react equivalent
React re-renders can trigger FSL when effects measure the DOM; useLayoutEffect reads pre-paint by design.

Legacy vs modern

Layout-thrashing read/write loop vs batched measurements

Interleaving geometry reads and style writes forces a synchronous reflow per iteration; batching reads and writes collapses the work into one or two flushes.

before → after
Read/write thrashing loop
for (const el of els) {
  const w = el.offsetWidth; // ❌ OLD: forces layout (dirty from previous write)
  el.style.width = (w + 10) + 'px'; // write dirties layout again
}
Batched read-then-write
const widths = [...els].map(el => el.offsetWidth); // ✅ NEW: all reads together
els.forEach((el, i) => { el.style.width = (widths[i] + 10) + 'px'; }); // ✅ NEW: all writes

Interview gotchas

Context

This is the core performance question about layout — interviewers want the dirty-flag mechanism and the concrete list of properties that force reflow, not just 'reads are slow'.

The mechanical answer

A forced synchronous layout (FSL) happens when you read a geometry-dependent property while the layout is dirty: the browser cannot return a current value without first running the layout pass, so it flushes synchronously, regardless of any batching. Triggering properties include offsetTop/Left/Width/Height, clientTop/Left/Width/Height, getBoundingClientRect(), getComputedStyle() (for some properties), scrollTop/Left/Width/Height, and range/selection geometry. In a loop that alternates writes and these reads, every iteration forces a reflow — the worst pattern. Avoiding FSL means separating reads from writes (batch them), caching measurements, and using APIs that report without forcing layout synchronously (IntersectionObserver, ResizeObserver, or rAF-timed measurement).

Trap

The naive answer is 'layout happens once when the page loads'. Layout is lazy and flushes on reads or before paint. Another trap: thinking getComputedStyle is always safe — reading layout-dependent properties from it also forces layout. Candidates who list the offending properties and the dirty-flag flush mechanism show real performance understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.