Script Valley
Web Performance Fundamentals
CSS Performance and RenderingLesson 6.3

What causes forced synchronous layout and how to avoid it

forced synchronous layout, layout thrashing, read-write batching, offsetHeight, getBoundingClientRect, FastDOM library, requestAnimationFrame batching

Forced Synchronous Layout

Forced synchronous layout (FSL) occurs when JavaScript writes to the DOM and then immediately reads a layout property, forcing the browser to flush its pending layout queue to return an accurate value. Do this in a loop and you create layout thrashing — one of the worst performance patterns.

// ✗ Layout thrashing — forces layout on every iteration
const items = document.querySelectorAll('.item');
items.forEach(item => {
  const width = item.offsetWidth; // READ forces pending layout flush
  item.style.width = width * 2 + 'px'; // WRITE invalidates layout
});

// ✓ Batch reads first, then writes
const widths = Array.from(items).map(item => item.offsetWidth); // all READs
items.forEach((item, i) => {
  item.style.width = widths[i] * 2 + 'px'; // all WRITEs
});

Properties that trigger FSL when read after a write: offsetWidth, offsetHeight, getBoundingClientRect(), scrollTop, clientWidth, getComputedStyle().

The DevTools Performance panel flags FSL with a red triangle on the Layout record. Look for alternating purple (layout) and yellow (script) bars — that's thrashing.

// Wrap FSL-prone code in rAF to batch inside a single frame
requestAnimationFrame(() => {
  const measurements = items.map(el => el.getBoundingClientRect());
  items.forEach((el, i) => applyLayout(el, measurements[i]));
});

The FastDOM library enforces read/write batching API-level to prevent FSL by design in larger codebases.

Up next

How to use CSS containment to improve rendering performance

Sign in to track progress