What's the Difference Between Reflow and Repaint? Explaining DOM Layout vs. Rendering in Web Performance

In the world of web development, creating fast, responsive websites is paramount. Users expect smooth interactions, quick load times, and jank-free experiences. A critical aspect of web performance lies in understanding how browsers render content—and two key processes in this pipeline are reflow and repaint.

Often used interchangeably, reflow and repaint are distinct stages in the browser’s rendering cycle, each with unique triggers, costs, and impacts on performance. Confusing them can lead to inefficient code, slow interactions, and frustrated users.

In this blog, we’ll demystify reflow and repaint, explore their roles in the browser’s rendering pipeline, and share practical strategies to minimize their impact on your website’s performance.

Table of Contents#

  1. The Critical Rendering Path: A Primer
  2. Understanding Reflow (Layout)
    • What is Reflow?
    • When Does Reflow Occur?
    • Why Reflow Is Computationally Expensive
  3. Understanding Repaint (Painting)
    • What is Repaint?
    • When Does Repaint Occur?
    • How Repaint Differs from Reflow
  4. Key Differences Between Reflow and Repaint
  5. How Reflow and Repaint Impact Web Performance
  6. Practical Tips to Minimize Reflow and Repaint
  7. Real-World Examples
  8. Conclusion
  9. References

The Critical Rendering Path: A Primer#

Before diving into reflow and repaint, let’s briefly recap the Critical Rendering Path—the sequence of steps browsers follow to convert HTML, CSS, and JavaScript into pixels on the screen. This pipeline includes:

  1. HTML Parsing: Converts HTML into the Document Object Model (DOM), a tree structure representing the page’s content.
  2. CSS Parsing: Converts CSS into the CSS Object Model (CSSOM), a tree structure representing styles.
  3. Render Tree Construction: Combines the DOM and CSSOM into a Render Tree, which includes only visible elements (e.g., display: none elements are excluded).
  4. Layout (Reflow): Calculates the geometry of elements in the render tree (size, position, spacing, etc.).
  5. Paint (Repaint): Fills in pixels for each element (e.g., colors, shadows, gradients).
  6. Composite: Combines painted layers into the final screen image.

Reflow and repaint are steps 4 and 5 in this process. Let’s break them down.

Understanding Reflow (Layout)#

What is Reflow?#

Reflow (or layout) is the process where the browser calculates the exact position and size of every element in the render tree. It determines how elements are arranged on the page—their width, height, margins, padding, and how they interact with neighboring elements (e.g., floating, flexbox, grid).

Think of reflow as the browser solving a complex puzzle: "Given the styles and content, where should each piece go, and how big should it be?"

When Does Reflow Occur?#

Reflow is triggered whenever the browser needs to recalculate the layout of the page. Common triggers include:

  • Initial page load: The first render requires a full reflow to position all elements.
  • Window resizing: Changing the viewport size forces the browser to reflow all elements.
  • DOM manipulation: Adding, removing, or modifying elements (e.g., appendChild, removeChild).
  • Style changes affecting geometry: Modifying CSS properties that impact layout, such as width, height, margin, padding, top, left, display, or font-size.
  • Font changes: Loading new fonts or adjusting font-family/font-size affects text layout, triggering reflow.
  • Calculating layout properties via JavaScript: Reading properties like offsetHeight, getBoundingClientRect(), or scrollTop forces the browser to reflow immediately to return accurate values.

Why Reflow Is Computationally Expensive#

Reflow is one of the most resource-intensive steps in rendering because:

  • Layout is a tree: The render tree is hierarchical—parent elements affect their children. For example, resizing a parent <div> forces all its children to reflow. This "cascade" can lead to a chain of recalculations.
  • Large render trees: A page with thousands of elements (e.g., a long list or data table) requires reflowing all of them, which can take milliseconds or longer.
  • Precision: Browsers must calculate sub-pixel accuracy for smooth rendering, adding computational overhead.

Understanding Repaint (Painting)#

What is Repaint?#

Repaint (or painting) is the process where the browser fills in pixels for elements that have changed in appearance but not in geometry. It’s about how an element looks, not where it is.

For example, if you change an element’s color from blue to red, the browser doesn’t need to reflow (the element’s size/position stays the same), but it does need to repaint that element to update its color.

When Does Repaint Occur?#

Repaint is triggered in two scenarios:

  1. After reflow: Since reflow changes an element’s geometry, the browser must repaint the affected area to reflect the new layout.
  2. Visual style changes without layout impact: Modifying CSS properties that only affect appearance, such as color, background-color, box-shadow, visibility, or border-radius.

How Repaint Differs from Reflow#

While reflow calculates where elements go, repaint calculates what they look like. Repaint is generally faster than reflow because it skips the layout calculation step. However, it’s not free: repainting large areas (e.g., the entire viewport) or complex elements (e.g., gradients, shadows) can still be costly.

Key Differences Between Reflow and Repaint#

To clarify, here’s a side-by-side comparison:

AspectReflow (Layout)Repaint (Painting)
PurposeCalculates element geometry (size/position).Fills in pixels for visual appearance.
Triggered ByChanges to layout properties (e.g., width, margin).Changes to visual properties (e.g., color, background), or after reflow.
Computational CostHigh (hierarchical, cascading calculations).Lower than reflow (but still costly for large areas).
DependencyOften triggers repaint (new layout needs new pixels).Rarely triggers reflow (unless visibility: hiddenvisible, which may affect layout).

How Reflow and Repaint Impact Web Performance#

Frequent or unoptimized reflows and repaints are major culprits of poor web performance. Here’s why:

1. Jank and Dropped Frames#

Browsers aim to render at 60 frames per second (fps) for smooth interactions (e.g., scrolling, animations). This gives each frame ~16ms to complete (1000ms/60fps). If a reflow or repaint takes longer than 16ms, the browser drops frames, leading to jank—choppy animations or unresponsive scrolling.

2. Slow Interactions#

User inputs (e.g., clicking a button, typing in a form) often trigger reflows/repaints. If these processes are slow, interactions feel laggy, frustrating users.

3. Poor Core Web Vitals#

Core Web Vitals (Google’s performance metrics) like Largest Contentful Paint (LCP) and Interaction to Next Paint (INP) are directly impacted. For example:

  • LCP measures when the largest content element is rendered; a slow initial reflow delays LCP.
  • INP measures responsiveness to user input; frequent reflows during interactions (e.g., filtering a list) increase INP.

Practical Tips to Minimize Reflow and Repaint#

The goal is to reduce the frequency and cost of reflows/repaints. Here are actionable strategies:

1. Batch DOM Changes#

Frequent DOM updates (e.g., adding multiple elements) trigger multiple reflows. Instead, batch changes offscreen using a DocumentFragment or hidden element, then append once:

// Bad: Triggers multiple reflows
const list = document.getElementById("myList");
for (let i = 0; i < 100; i++) {
  const item = document.createElement("li");
  list.appendChild(item); // Reflow triggered each time!
}
 
// Good: Batch changes, single reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const item = document.createElement("li");
  fragment.appendChild(item); // No reflow (offscreen)
}
document.getElementById("myList").appendChild(fragment); // Single reflow

2. Avoid Forced Synchronous Layouts#

Reading layout properties (e.g., offsetHeight) and immediately writing to the DOM forces the browser to reflow synchronously, causing "layout thrashing."

// Bad: Read → Write → Read → Write (triggers multiple reflows)
for (let i = 0; i < 100; i++) {
  const height = element.offsetHeight; // Read (triggers reflow)
  element.style.height = height + 10 + "px"; // Write (triggers reflow)
}
 
// Good: Read all first, then write (single reflow)
const heights = [];
for (let i = 0; i < 100; i++) {
  heights.push(element.offsetHeight); // Read all first
}
for (let i = 0; i < 100; i++) {
  element.style.height = heights[i] + 10 + "px"; // Write all at once
}

3. Use CSS Containment#

The contain property tells the browser an element’s layout, paint, or size is independent of the rest of the page, limiting reflow/repaint scope:

.isolated-element {
  contain: layout paint size; /* Limits reflow/repaint to this element */
}

4. Animate with transform and opacity#

CSS transform (e.g., translate, scale) and opacity are handled by the browser’s compositor thread, not the main thread. They don’t trigger reflow or repaint—only composite (the final step in rendering).

/* Good: Uses compositor thread (no reflow/repaint) */
.animated-element {
  transition: transform 0.3s;
}
.animated-element:hover {
  transform: translateX(10px); /* No reflow/repaint! */
}
 
/* Bad: Triggers reflow on hover */
.animated-element {
  transition: left 0.3s;
}
.animated-element:hover {
  left: 10px; /* Triggers reflow */
}

5. Minimize Paint Areas#

Avoid repainting large regions. For example, use will-change: transform to hint to the browser that an element will animate, allowing it to optimize (e.g., promote the element to its own layer):

.animated-element {
  will-change: transform; /* Hints to browser for optimization */
}

Real-World Examples#

Example 1: Layout Thrashing (Bad Practice)#

This code reads and writes layout properties in a loop, causing multiple reflows:

// Causes layout thrashing!
const box = document.querySelector(".box");
for (let i = 0; i < 100; i++) {
  const width = box.offsetWidth; // Read (reflow)
  box.style.width = width + 10 + "px"; // Write (reflow)
}

Fix: Batch reads first, then writes:

const box = document.querySelector(".box");
const widths = [];
// Read all values first
for (let i = 0; i < 100; i++) {
  widths.push(box.offsetWidth);
}
// Write all values at once
for (let i = 0; i < 100; i++) {
  box.style.width = widths[i] + 10 + "px";
}

Example 2: Inefficient Hover Effect (Bad Practice)#

This hover effect changes box-shadow, triggering repaint on every hover:

.card {
  transition: box-shadow 0.3s;
}
.card:hover {
  box-shadow: 0 10px 20px rgba(0,0,0,0.2); /* Triggers repaint */
}

Fix: Use transform: translateZ(0) to promote the card to its own layer, reducing repaint scope (modern browsers may auto-optimize this, but explicit hints help):

.card {
  transition: box-shadow 0.3s;
  transform: translateZ(0); /* Promotes to own layer */
}

Conclusion#

Reflow and repaint are critical stages in the browser’s rendering pipeline, but they differ in purpose, cost, and triggers. Reflow calculates layout (size/position) and is computationally expensive, while repaint fills in pixels (appearance) and is cheaper but still impactful.

By understanding these processes and following optimization strategies—like batching DOM changes, using transform for animations, and minimizing paint areas—you can drastically improve your website’s performance, reduce jank, and deliver a smoother user experience.

Remember: Every millisecond counts. Optimize reflows and repaints, and your users will thank you.

References#