Back to Browser Internals
Browser Internals
medium
mid

What is the difference between reflow and repaint, and when does each happen in the browser?

**Reflow (layout)**: recompute box geometry — triggered by anything that changes size/position (width, font-size, DOM insertion, viewport resize). Expensive. **Repaint**: redraw pixels without re-layout — triggered by color/visibility changes. Cheap-ish. **Composite-only** (transform/opacity on a GPU-promoted layer) avoids both. Animate with transform + opacity for 60fps.

3 min read·~6 min to think through

Three levels of render work. Each level is much more expensive than the next.

1. Layout (reflow)

Recompute the box model — position, size of every affected element. Triggered by:

  • Geometry changes (width, height, padding, margin, border).
  • Font changes affecting size.
  • DOM insertion / removal.
  • Reading layout properties (offsetWidth, getBoundingClientRect, scrollHeight) — forces sync layout if styles are dirty.
  • Viewport resize.
  • display toggles.

Cost is roughly O(n) in affected nodes; cascades down the tree.

2. Paint (repaint)

Convert layout boxes to pixels. Triggered by:

  • color, background, box-shadow, visibility, outline, border-radius.
  • Anything visual that doesn't change geometry.

Cheaper than layout but still on the main thread.

3. Composite

Combine layers (GPU). Free-ish — runs off the main thread.

transform and opacity on an element promoted to its own layer (will-change: transform or 3D transform) skip layout and paint entirely. This is why all smooth animations use transform: translateX() instead of left.

Decision table

PropertyTriggers
width / height / top / left / margin / paddingLayout + paint + composite
color / background / shadow / outlinePaint + composite
transform / opacity (on its own layer)Composite only

Forced sync layout

js
// Bad
for (const el of items) {
  el.style.width = el.offsetWidth + 10 + "px";  // read after write → sync layout per iter
}

// Good
const widths = items.map((el) => el.offsetWidth);   // all reads first
items.forEach((el, i) => { el.style.width = widths[i] + 10 + "px"; });

Read all → write all. Batching avoids the per-iter layout flush.

Tools

  • DevTools Performance "Recalculate Style" / "Layout" / "Paint" / "Composite Layers" rows.
  • Layers panel to see promoted layers.
  • Rendering > Paint flashing to see which areas repaint.

Practical rules

  • Animate transform + opacity, never top/left/width.
  • Use contain: layout style paint to isolate subtrees.
  • Avoid reading layout properties in a loop after writes.
  • Use requestAnimationFrame for visual updates.
  • Avoid width: auto + content change in scroll containers.

Interview framing

"Three tiers: layout (reflow) recomputes geometry, paint redraws pixels, composite combines layers. Layout is the expensive one — anything geometric (width, font, DOM insertion) triggers it. Paint is mid (color, shadow). Composite (transform/opacity on a promoted layer) is GPU work and effectively free. So animate with transform and opacity for 60fps. Forced sync layout from read-after-write in loops is a classic perf bug — batch reads then writes. contain isolates subtrees so layout work doesn't cascade. DevTools Performance shows exactly which tier you're paying."

Follow-up questions

  • Why does transform skip layout?
  • What is forced sync layout?
  • When would you use will-change?

Common mistakes

  • Animating top/left instead of transform.
  • Reading offsetWidth in a write loop.
  • Overusing will-change (memory cost).

Performance considerations

  • Composite-only animations hit 60fps; layout animations stutter. Batch reads/writes. Use `contain` to scope work.

Edge cases

  • will-change creates a layer; too many layers blow VRAM.
  • iOS triggers layer promotion on 3D transforms.
  • Sticky positioning triggers layout per scroll on some browsers.

Real-world examples

  • Smooth scrolling animations, modal open transitions, drag-and-drop libs (transform-based).

Senior engineer discussion

Seniors profile in DevTools, identify layout vs paint vs composite per interaction, and refactor to composite-only animations where possible.

Related questions