Under The Hood
frontend browser Chromium rendering architecture

Main Thread vs Compositor Thread, GPU Tiling & Paint Worklets

Last updated
Prerequisites:
The rendering pipeline (layout, paint)
Understanding of browser threads and GPU
  • browser
  • compositor
  • gpu
  • rasterization
  • tiling
  • threads

Read at your depth

The practical view

The browser uses multiple threads: the main thread (JS, style, layout, paint recording) and the compositor thread (GPU-friendly layer placement). Animating transform or opacity is handled by the compositor: it just repositions existing layer textures, so it runs on the compositor thread and stays smooth even when the main thread is busy. Animating width, height, or top forces the main thread to re-run layout and paint, then the compositor re-uploads — jank risk. Rasterization converts paint commands into pixels (often on the GPU via tiles). Paint Worklets (CSS Paint API) let you generate images procedurally for properties like background-image.

The same idea in other frameworks

react equivalent
React's concurrent rendering keeps JS off the critical path but cannot help compositor-vs-main work; transform animations still win.

Legacy vs modern

Animating layout properties vs compositor-only transform

Animating left/top/width forces main-thread layout+paint every frame; animating transform runs entirely on the compositor thread.

before → after
Animating layout properties
el.animate([{ left: '0px' }, { left: '200px' }], { duration: 1000 }); // ❌ OLD: layout per frame
Compositor-only transform animation
el.animate([{ transform: 'translateX(0)' }, { transform: 'translateX(200px)' }], { duration: 1000 }); // ✅ NEW

Interview gotchas

Context

This tests the compositing architecture — interviewers want the layer/texture model and the thread separation, not just 'transforms are fast'.

The mechanical answer

When an element is composited (promoted to its own layer), its rendering is captured as a GPU texture. The compositor thread owns the layer tree and draws each layer as a quad with a transform and opacity. Animating transform or opacity only changes those compositor-side values (via a cc::Animation sampled on the compositor thread), so no main-thread style/layout/paint runs — the existing texture is just repositioned/blended each frame. This is why these animations stay smooth even when the main thread is blocked: they don't need it. Animating any layout-affecting property (width, height, top, left) invalidates the layer's content, forcing main-thread layout + paint + re-raster + texture upload, which is both slow and main-thread-bound. Layer promotion has memory cost (a full texture per layer), which is why overusing will-change can hurt.

Trap

The naive answer is 'transforms are GPU-accelerated so they're always free'. They need the element to be on a composited layer, and the texture memory is a real cost. Another trap: thinking animating width is 'GPU-accelerated too' because some engines do partial optimization — geometry animations still engage layout, and layout-invalidating animations can't be compositor-driven. Candidates who mention texture upload and layer promotion show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.