Get in Touch
Layout Thrashing vs Forced Reflow vs Reflow: What's the Difference? - Red Surge Technology Blog
JavaScriptPerformanceBrowserWeb DevelopmentOptimization

If you've spent any time debugging browser performance, you've seen these three terms. Reflow. Forced reflow. Layout thrashing. They show up in Chrome DevTools warnings, Lighthouse reports, and blog posts about making websites faster. And they're often used as if they mean the same thing.

They don't. They describe three related but distinct problems, and understanding the difference changes how you diagnose and fix them.

Here's the short version. Reflow is a normal part of rendering. It happens constantly and isn't a problem by itself. Forced reflow is when your JavaScript asks the browser for layout information at an inconvenient moment, forcing it to recalculate geometry synchronously. Layout thrashing is when you do that in a loop, creating a cascade of forced reflows that tanks performance.

If you've read our forced reflow guide, you have the basics. This post is the comparison—what each term actually means, why the distinction matters, and how to recognize which one you're dealing with.

What Reflow Actually Is

Reflow is the browser recalculating the position and size of every element on the page. It's also called layout in some contexts, and the two terms are essentially interchangeable. When the browser needs to know where things are and how big they are, it performs a reflow.

Reflow happens constantly. Every time you change something that affects layout—adding an element, changing a font size, resizing a container, modifying a class that affects geometry—the browser schedules a reflow. It doesn't run immediately. It batches changes and runs the reflow once, at the next opportunity, typically before the next paint.

This batching is a feature, not a bug. If you change five elements' widths in a row, the browser doesn't reflow five times. It queues the changes, then reflows once when it needs to display the result. That single reflow is fast. For a typical page, it takes a few milliseconds.

A single reflow is normal. Your application triggers hundreds of them during a typical session, and you never notice. The problems start when reflows become frequent, expensive, or synchronous.

What Forced Reflow Actually Is

Forced reflow happens when JavaScript reads a layout-dependent property after the browser has pending layout changes but before the browser has had a chance to reflow. The read forces the browser to flush its queue and compute layout immediately, synchronously, so it can return accurate values.

The properties that trigger forced reflow are the ones that depend on layout: offsetHeight, offsetWidth, offsetTop, offsetLeft, clientHeight, clientWidth, scrollHeight, scrollWidth, scrollTop, scrollLeft, getBoundingClientRect(), getComputedStyle(), and a handful of others.

Here's the simplest example:

// Write to the DOM
element.style.width = "200px";

// Read a layout property
const height = element.offsetHeight; // Forced reflow

That element.offsetHeight read forces the browser to recalculate layout immediately, even though it just scheduled a reflow from the width change. The browser had to "flush" its pending changes to give you an accurate number.

A single forced reflow isn't the end of the world. It's a few milliseconds wasted. The problem is when forced reflows happen repeatedly, or when they happen inside loops that process many elements.

Chrome DevTools reports forced reflow as "Forced reflow while executing JavaScript" or "Forced synchronous layout." It's a warning, not an error. It's telling you that the browser had to do extra work because your code read layout at the wrong moment.

What Layout Thrashing Actually Is

Layout thrashing is the pathological pattern. It's what happens when you interleave reads and writes in a loop, forcing the browser to reflow repeatedly for the same batch of changes.

Here's the classic example:

const elements = document.querySelectorAll(".item");

for (const el of elements) {
  // Read (forces reflow if there are pending changes)
  const width = el.offsetWidth;

  // Write (invalidates layout)
  el.style.width = `${width + 10}px`;
}

Each iteration reads offsetWidth and writes style.width. The read forces a reflow of everything that's pending. The write invalidates the layout. The next read forces another reflow. With 100 elements, you get 100 forced reflows instead of one batched reflow.

That's thrashing. The browser is constantly recalculating layout because you keep changing things and then asking for layout information. The name is apt—the browser is thrashing between calculation and invalidation.

In Chrome DevTools, layout thrashing shows up as multiple forced reflows in quick succession, often inside the same function call. The Performance panel will show a long "Recalculate Style" or "Layout" block with many sub-entries. It's one of the most common causes of slow JavaScript execution on pages that manipulate the DOM heavily.

The Side-by-Side Comparison

Let me lay out the three terms clearly.

Term What It Is When It Happens Is It a Problem?
Reflow Browser recalculating layout Any layout-affecting change No—it's normal and batched
Forced Reflow Synchronous layout triggered by a read Reading layout after writing Sometimes—only if excessive
Layout Thrashing Repeated forced reflows in a loop Interleaving reads and writes Yes—always a performance problem

The distinction matters because the fixes are different. If you have a single forced reflow, you can often ignore it. If you have layout thrashing, you need to restructure your code.

How to Recognize Which One You Have

Chrome DevTools is your best diagnostic tool. Open the Performance panel, record a session, and look for these patterns.

Single forced reflow. A short "Recalculate Style" or "Layout" entry in the flame chart, with "Forced reflow while executing JavaScript" in the warnings panel. This is usually fine. If it happens once, it's a minor cost.

Multiple forced reflows. Several "Recalculate Style" or "Layout" entries clustered together, each triggered by JavaScript. You're probably doing something in a loop that could be restructured.

Layout thrashing. A long sequence of "Recalculate Style" and "Layout" entries interleaved with JavaScript execution, often within the same function. The total time spent in layout is significant—hundreds of milliseconds or more. This is the pattern that needs to be fixed.

You can also use the performance.measure() API to instrument specific functions and see how much time they spend in layout. But in practice, the DevTools Performance panel gives you enough information to diagnose the problem.

If you've been following our performance series, from why modern websites feel slower to improving page speed for SEO, you know that these rendering issues are one of the most common causes of perceived slowness. Layout thrashing is the worst offender because it multiplies the cost.

How to Fix Each One

The fixes differ depending on which problem you have.

Fixing forced reflow

A single forced reflow isn't usually worth fixing. The cost is a few milliseconds, and the code is often clearer when you read layout properties naturally.

That said, if you're doing forced reflows frequently—say, in a scroll handler or a resize handler—you can reduce them by caching the values you need. Read all the layout properties you need once, store them in variables, and use the cached values instead of re-reading.

// Before: forced reflow on every call
function handleScroll() {
  const height = container.offsetHeight; // Forced reflow
  doSomething(height);
}

// After: cache and reuse
let cachedHeight;
function handleScroll() {
  if (cachedHeight === undefined) {
    cachedHeight = container.offsetHeight;
  }
  doSomething(cachedHeight);
}

If the container's size can change, invalidate the cache when appropriate. But for many cases, the size is stable during a single interaction, and caching eliminates the forced reflow.

Fixing layout thrashing

Layout thrashing requires restructuring the code. The fix is to separate reads from writes—do all the reads first, then all the writes.

// Before: thrashing
const elements = document.querySelectorAll(".item");
for (const el of elements) {
  const width = el.offsetWidth; // Read
  el.style.width = `${width + 10}px`; // Write
}

// After: batched reads and writes
const elements = document.querySelectorAll(".item");
const widths = Array.from(elements).map((el) => el.offsetWidth); // All reads

elements.forEach((el, i) => {
  el.style.width = `${widths[i] + 10}px`; // All writes
});

Now the browser reads all the layout properties in one batch—causing a single forced reflow—and then applies all the writes in another batch. The result is one reflow instead of N reflows.

For more complex cases, use requestAnimationFrame to batch writes to the next frame:

const elements = document.querySelectorAll(".item");
const widths = Array.from(elements).map((el) => el.offsetWidth);

requestAnimationFrame(() => {
  elements.forEach((el, i) => {
    el.style.width = `${widths[i] + 10}px`;
  });
});

The reads happen synchronously, and the writes are deferred to the next frame. This keeps the reads and writes separate and prevents thrashing.

If you're using event handlers that trigger layout, consider throttling or debouncing them, as we covered in our JavaScript debounce vs throttle guide. A scroll handler that runs 60 times per second and triggers thrashing is a disaster. A debounced handler that runs once every 100ms is manageable.

Fixing reflow (when it's actually a problem)

If reflow itself is a problem—because the page has thousands of elements or a deeply nested DOM—the fixes are structural. Reduce the complexity of the DOM. Use CSS properties that don't trigger reflow when possible (transform and opacity are the classic examples). Avoid changing layout-affecting properties in tight loops.

The Performance panel will show you when reflow is taking too long. If you see a "Recalculate Style" block that takes more than a few milliseconds, investigate the DOM structure and the CSS involved.

A Real Story: Debugging a Sluggish Table

A few months ago, I worked on a dashboard that displayed a table with 200 rows. Users reported that filtering the table felt sluggish—a noticeable delay of several hundred milliseconds after typing in the search box.

The search box had a handler that filtered the visible rows. The filtering logic was fast—it just toggled a CSS class on each row. But the browser was slow to reflect the changes.

I recorded a performance profile and found the problem immediately. The handler was doing this:

rows.forEach((row) => {
  const isMatch = row.textContent.includes(query);
  row.style.display = isMatch ? "" : "none"; // Write
  const height = row.offsetHeight; // Read (forces reflow)
  updateRowHighlight(row, height); // Uses the height
});

Each iteration wrote to style.display and then read offsetHeight. That's textbook layout thrashing. With 200 rows, the browser was doing 200 forced reflows.

The fix was straightforward. I split the reads from the writes:

const visibility = rows.map((row) => row.textContent.includes(query));
const heights = rows.map((row, i) => {
  row.style.display = visibility[i] ? "" : "none";
  return row.offsetHeight;
});
// Then use heights for the highlight logic

Wait—that still reads after writing. Let me redo that properly:

// Read phase: get all the heights first
const heights = rows.map((row) => row.offsetHeight);

// Write phase: apply all the changes
rows.forEach((row, i) => {
  const isMatch = row.textContent.includes(query);
  row.style.display = isMatch ? "" : "none";
  updateRowHighlight(row, heights[i]);
});

Now the reads all happen before any writes. The browser does one reflow during the read phase—capturing the current state—and then batches all the writes into a single reflow at the end. The filtering went from several hundred milliseconds to under 50ms. The difference was dramatic and immediately noticeable to users.

The lesson: layout thrashing is often hiding in code that looks reasonable. You have to know what to look for.

Tools for Detecting These Problems

Chrome DevTools Performance panel is the primary tool. Record a session, look for "Recalculate Style" and "Layout" entries, and check the warnings for "Forced reflow while executing JavaScript."

Lighthouse reports "Avoid large layout shifts" and can flag forced reflows, though its detection is less granular than DevTools.

The PerformanceObserver API lets you monitor long tasks programmatically and log them for analysis.

Web Vitals in Chrome measures Cumulative Layout Shift (CLS), which isn't the same as layout thrashing but is related—both involve layout instability. Our guide on improving page speed for SEO covers how these metrics affect search rankings.

For React applications, the React DevTools Profiler can help identify components that trigger excessive re-renders, which often correlate with layout thrashing. Our guide on preventing unnecessary re-renders in React covers the React-specific patterns.

Wrapping Up

Reflow, forced reflow, and layout thrashing are related but distinct. Reflow is normal. Forced reflow is a read after a write. Layout thrashing is repeated forced reflows in a loop.

The practical takeaway: if you see a single forced reflow, don't worry about it. If you see many forced reflows, look for loops that read and write. If you see layout thrashing, restructure your code to batch reads and writes separately.

For a deeper dive into forced reflow specifically, see our forced reflow guide. For the broader performance context, see our posts on why modern websites feel slower and improving page speed for SEO.

Now go find those loops.


Need help diagnosing rendering performance issues in your web application? Red Surge Technology specializes in performance audits and optimization for modern web apps. Get in touch to discuss your project.