Skip to content

Rendering Pipeline: Layout, Paint, Composite (and How to Avoid Jank)

A practical mental model of the browser rendering pipeline and how common coding patterns cause forced reflow, jank, and slow interactions.

Rajesh DhimanMarch 01, 2026~16 min
On this page

30‑Second Senior Interview Answer

The browser rendering engine converts raw HTML, CSS, and JavaScript into visible pixels on screen through the Critical Rendering Path (CRP) in 5 distinct phases:

  1. DOM & CSSOM Construction: Parsing markup and styling into hierarchical object models.
  2. Render Tree Generation: Combining DOM and CSSOM to represent only visible elements (display: none is excluded; visibility: hidden is retained).
  3. Layout (Reflow): Calculating the exact coordinates, box model, and geometric dimensions of every node.
  4. Paint (Rasterization): Converting vector elements into raster pixels across multiple layers (colors, borders, shadows).
  5. Compositing: Uploading paint layers as textures to the GPU, where they are combined and drawn to the display buffer.

Jank (dropped frames below 60fps / 16.6ms budget) is primarily caused by Layout Thrashing (Forced Synchronous Reflow)—interleaving DOM style writes with geometry reads in tight loops.


1. The Critical Rendering Path (CRP)

flowchart TD
    HTML["HTML Source"] --> DOM["DOM Tree"]
    CSS["CSS Source"] --> CSSOM["CSSOM Tree"]
    DOM --> RT["Render Tree<br/>(Visible Nodes)"]
    CSSOM --> RT
    RT --> Layout["Layout (Reflow)<br/>(Compute Coordinates & Dimensions)"]
    Layout --> Paint["Paint (Rasterization)<br/>(Fill Pixels & Colors onto Layers)"]
    Paint --> Composite["Composite<br/>(GPU Tiles Combined on Screen)"]

Key Render Tree Nuance (Interview Favorite)

  • Elements styled with display: none are completely excluded from the Render Tree (no layout or paint cost).
  • Elements styled with visibility: hidden or opacity: 0 are included in the Render Tree because they still occupy physical layout space.
  • Elements like <head>, <script>, and <meta> are never part of the Render Tree.

2. Property Cost Hierarchy: The 3 Rendering Tiers

Understanding which CSS properties trigger which stages in the pipeline is essential for 60fps/120fps UI performance:

flowchart LR
    subgraph Tier1["1. Full Pipeline (Slowest)"]
        T1["Layout → Paint → Composite<br/>e.g. width, height, margin, top, left"]
    end
    subgraph Tier2["2. Paint Only (Moderate)"]
        T2["Paint → Composite<br/>e.g. color, background, box-shadow"]
    end
    subgraph Tier3["3. Compositor Only (Fastest)"]
        T3["Composite Only (GPU)<br/>e.g. transform, opacity, filter"]
    end

1. Layout-Triggering Properties (Full Pipeline: Expensive)

Modifying any geometric property forces the browser to recalculate positions for that element and all preceding/following siblings in the document flow:

  • width, height, min-width, padding, margin
  • top, left, right, bottom
  • font-size, font-family, line-height
  • display, position, flex-direction, grid-template

2. Paint-Triggering Properties (Skips Layout: Moderate)

Changing visual properties that do not alter geometry allows the browser to skip layout and jump directly to painting:

  • color, background-color, background-image
  • box-shadow, text-shadow
  • border-radius, outline

3. Compositor-Only Properties (Skips Layout & Paint: 60fps Budget)

Modifying properties that can be calculated entirely on the GPU without recalculating geometry or re-rasterizing pixels:

  • transform (translate3d, scale, rotate)
  • opacity
  • filter

[!TIP] Staff Rule of Thumb: For smooth animations, UI interactions, and drag handles, strictly animate transform and opacity. Never animate top/left or width/height.


3. Forced Synchronous Layout & Layout Thrashing

Browsers are lazy by design: when you modify DOM styles in JavaScript, the browser buffers those changes and plans to run a single Layout pass at the end of the current microtask frame.

The Forced Synchronous Reflow Trap

If your JavaScript reads geometric properties immediately after modifying styles, the browser cannot use cached geometry. It is forced to flush the pipeline immediately and synchronously compute layout:

// ❌ LAYOUT THRASHING: Reading and writing in a tight loop
const items = document.querySelectorAll('.card');
 
items.forEach((item) => {
  // 1. READ (forces synchronous layout recalculation)
  const width = item.offsetWidth;
 
  // 2. WRITE (invalidates layout for next iteration)
  item.style.width = `${width + 10}px`;
});

If there are 100 items, the browser executes 100 consecutive synchronous layout recalculations, freezing the main thread for hundreds of milliseconds.

List of Layout-Forcing Read Properties

Accessing any of these properties on a "dirty" DOM forces an immediate reflow:

  • elem.offsetWidth, elem.offsetHeight, elem.offsetTop, elem.offsetLeft
  • elem.scrollWidth, elem.scrollHeight, elem.scrollTop, elem.scrollLeft
  • elem.clientGeometry (clientTop, clientLeft, clientWidth, clientHeight)
  • elem.getBoundingClientRect()
  • window.getComputedStyle(elem)
  • window.scrollY, window.scrollX

4. Whiteboard Solution: Read-Write Batching & requestAnimationFrame

To fix layout thrashing, batch all reads first, then execute all writes:

// ✅ OPTIMIZED: Separating reads from writes
const items = document.querySelectorAll('.card');
 
// Phase 1: BATCH ALL READS (single layout read)
const widths = Array.from(items).map((item) => item.offsetWidth);
 
// Phase 2: BATCH ALL WRITES in the next animation frame
requestAnimationFrame(() => {
  items.forEach((item, index) => {
    item.style.width = `${widths[index] + 10}px`;
  });
});

FastDOM Pattern

Libraries like fastdom automate this queueing:

fastdom.measure(() => {
  const width = element.offsetWidth;
  fastdom.mutate(() => {
    element.style.width = `${width + 10}px`;
  });
});

5. GPU Layer Promotion & The Memory Trap

When an element is promoted to its own compositing layer, the browser renders its pixels once, uploads them as a texture to the GPU, and performs transformations directly on the GPU without repainting.

Promoting an Element to a Compositing Layer

.hardware-accelerated {
  will-change: transform;
  /* Legacy fallback: transform: translateZ(0); */
}

The Layer Over-Promotion Anti-Pattern

Candidates often believe that adding will-change: transform to everything makes the app faster. In reality:

  1. GPU Texture Memory Exhaustion: Every layer consumes significant VRAM. On mobile devices, over-promoting elements causes out-of-memory browser crashes.
  2. Layer Tree Bloat: The browser compositor must track stacking contexts, z-indexes, and layer overlap boundaries.
  3. Blurry Text: Elements on separate compositing layers can suffer from degraded subpixel text anti-aliasing.

[!WARNING] Use will-change sparingly, and only apply it when an element is actively animating (or immediately before animation starts via hover/touchstart), then remove it after animation concludes.


6. Modern CSS Performance: content-visibility

Modern browsers allow developers to bypass the layout and paint stages completely for offscreen elements using CSS containment:

.long-feed-article {
  /* Skips layout, paint, and style calculation until scrolled into view */
  content-visibility: auto;
  /* Reserves placeholder height to prevent scrollbar jumping (CLS) */
  contain-intrinsic-size: 0 450px;
}

CSS Containment (contain)

.card-widget {
  /* Isolates internal DOM changes from triggering parent layout or paint */
  contain: layout paint;
}

Summary Checklist

  • Critical Rendering Path: DOM + CSSOM → Render Tree → Layout → Paint → Composite.
  • display: none is omitted from the Render Tree; visibility: hidden is retained.
  • Layout triggers reflow; Paint modifies pixels; Compositing modifies GPU layer textures.
  • Strictly animate transform and opacity to stay within the 16.6ms 60fps frame budget.
  • Avoid layout thrashing: never interleave geometry reads (offsetWidth, getBoundingClientRect) with style writes.
  • Do not over-use will-change; GPU texture memory is finite and expensive.
  • Leverage content-visibility: auto to skip offscreen rendering on long pages.

Written by Rajesh Dhiman at Eunix Tech.

Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.