Browser Rendering
The step-by-step process a browser follows to turn HTML and CSS into the pixels you actually see on screen.
What is it?
Once a browser has an HTML document and its associated CSS, it still has real work to do before anything appears on screen. It has to figure out what each element is, what it should look like, where exactly it belongs, and finally draw all of that in the right place. This whole sequence is called rendering.
It roughly happens in four stages. First, the browser reads through the HTML and builds that live tree of elements — the DOM. At the same time, it reads through the CSS and figures out which style rules apply to which elements. Combining those two produces a tree of exactly which elements will actually be visible and what styles apply to each — this combined structure is often called the render tree.
Next, the browser works out layout: the exact size and position of every visible element on the page, in pixels — how wide is this box, how tall, where does it sit relative to everything around it. Finally comes paint: the browser actually fills in pixels — colors, text, images, borders — according to the layout it just calculated, and the picture appears on screen.
Any time something changes later — new content arrives, CSS is modified, the window is resized — the browser may need to redo some or all of these steps to keep the screen accurate.
Explain like I'm 10
It's like building a stage set: first you decide which props are actually going to be used (render tree), then you measure exactly where each one goes on the floor (layout), then stagehands actually place and paint everything into position (paint) so the audience sees the finished scene.
Examples
The stages, named
HTML ──┐
├──> Render Tree ──> Layout ──> Paint ──> Pixels on screen
CSS ──┘HTML and CSS are combined into a render tree of visible elements and their styles, then the browser computes sizes and positions (layout), then it fills in the actual pixels (paint).
A change that triggers re-rendering
// This changes an element's width — the browser
// must recalculate layout for anything nearby,
// then repaint the affected area.
box.style.width = "400px";Not every change is equally expensive. Changing something that affects size or position (like width) forces the browser to redo layout, while changing something purely visual (like color) can sometimes skip straight to repainting.
How it works
The browser doesn't wait for the entire HTML file to download before it starts working — it begins parsing HTML as it streams in, building the DOM incrementally. CSS is parsed similarly, and once enough of both is available, the browser can start constructing the render tree and calculating layout. Elements that CSS explicitly hides (like display: none) are excluded from the render tree entirely, since they never need a layout or paint step at all.
Parse HTML --> DOM tree \
>--> Render Tree --> Layout --> Paint
Parse CSS --> Style rules /Why does it exist?
Splitting rendering into distinct stages lets the browser be efficient about what it redoes when something changes. If only colors change, there's no need to recompute every element's size and position; if only one element's size changes, there's no need to repaint the entire screen. This staged pipeline is what keeps pages feeling responsive even as content updates constantly.
When to use it
Understanding this pipeline matters once you start caring about why a page feels slow or janky, especially when animating or updating the page frequently — knowing that some changes are cheap (paint-only) and others are expensive (trigger layout) shapes how you write both CSS and JavaScript for a smooth experience.
When not to use it
For basic page-building — writing HTML and CSS for a mostly static page — you don't need to think about the rendering pipeline directly; the browser handles it automatically. This mental model earns its keep mainly during performance work.
Common mistakes
Assuming every CSS change is equally cheap, when changes affecting size or position force a more expensive layout recalculation.
Thinking the whole page re-renders from scratch on every small update, rather than the browser recomputing only what actually changed.
Forgetting that elements hidden with display: none are skipped entirely during rendering, unlike elements merely made invisible in other ways.
Practice exercises
- Easy:
List the four main stages of the rendering pipeline in order, in your own words.
- Medium:
Explain why changing an element's color is typically cheaper for the browser than changing its width.
- Hard:
Using your browser's developer tools performance panel, record a page interaction and identify a 'layout' or 'paint' step in the recorded timeline.
Interview questions
What are the main stages of browser rendering?
Parsing HTML and CSS to build a render tree of the elements that will actually be visible along with their matched styles, computing layout — the exact size and position of each visible element — and then painting the actual pixels for colors, text, images, and borders.
What is the render tree?
A tree combining the DOM with the CSS rules that match each node, containing only the elements that will actually be rendered visually — elements excluded from display, like ones set to display: none, don't get a node in it at all.
Why does changing an element's width potentially cost more than changing its color?
Width affects that element's size, which can shift the position and size of surrounding elements too, so the browser has to redo layout for the affected part of the page before it can repaint. Color only affects how a box is painted, not its geometry, so the browser can often skip layout and jump straight to repainting.
What's the difference between 'layout' (also called reflow) and 'paint' (also called repaint)?
Layout is the step where the browser calculates the exact size and position, in pixels, of every visible element. Paint is the separate step after that, where the browser actually fills in pixels — colors, text, borders, images — based on the geometry layout already produced. A change can trigger both, just paint, or in some cases neither.
What is compositing, and how does it let some changes skip both layout and paint?
The browser can render certain elements onto separate layers that the GPU can move, scale, or fade independently of the rest of the page. If a change only affects one of these composited layers — like sliding it or changing its transparency — the browser can update it by recombining layers on the GPU, without recalculating layout or repainting pixels at all.
Why are `transform` and `opacity` recommended for animations over animating `top`/`left` or `width`/`height`?
transform and opacity can typically be handled entirely by compositing, skipping layout and paint. Animating top/left or width/height changes an element's actual geometry, forcing layout (and usually paint) to rerun on every single animation frame, which is far more expensive and more likely to drop frames.
What's the difference between `display: none` and `visibility: hidden`, in terms of rendering?
display: none removes the element from the render tree entirely — it has no box, takes up no space, and never reaches layout or paint. visibility: hidden keeps the element in the render tree and layout still reserves its space, it's just not painted, so surrounding elements don't shift into the gap it leaves behind.
How is `opacity: 0` different from `visibility: hidden`, even though both make something invisible?
Both skip painting the element visibly, but an opacity: 0 element is still fully present and interactive — it can still be clicked, focused, and tabbed to. A visibility: hidden element is also removed from the accessibility tree and cannot be focused or clicked, even though, like opacity: 0, it still occupies its layout space.
What is the Critical Rendering Path, and why does it matter for perceived load speed?
It's the sequence of steps — parsing HTML into a DOM, parsing CSS into matched styles, building the render tree, running layout, then painting — that has to complete before any pixels appear on screen. Anything that delays an early step in that chain (a slow stylesheet, a blocking script) delays every step after it, directly affecting how long a user stares at a blank screen.
Why is CSS treated as render-blocking by default?
The browser can't safely paint elements whose final appearance and layout it doesn't know yet — a stylesheet could change any element's size, position, or visibility. So by default, it holds off building the render tree and painting until it has parsed all the CSS it currently knows about, to avoid painting a page that then has to be redrawn a moment later.
Why can a `<script>` tag without `defer` or `async` block HTML parsing?
The browser has to assume a synchronous script might use document.write or otherwise depend on or modify the DOM as it exists at that exact point, so it pauses parsing the rest of the HTML, fetches and runs the script fully, and only then resumes building the DOM from where it left off.
What is 'layout thrashing,' and how does it happen?
It's when code repeatedly forces the browser to recalculate layout synchronously, over and over, inside a loop — typically by reading a layout-dependent property (like offsetHeight) right after writing a style change, which forces the browser to flush pending layout work immediately to give an up-to-date answer, instead of batching it once per frame as usual.
A loop reads `element.offsetWidth` and then sets `element.style.width` for each of 50 elements — why is this slow, and how would you fix it?
Reading offsetWidth forces the browser to make sure layout is current before answering, and the style write right after it invalidates layout again — so alternating reads and writes across 50 elements can force roughly 50 separate forced layout recalculations. Reading all the needed values first, then writing all the style changes afterward, lets the browser batch it into a single layout pass.
Does a reflow ever affect elements other than the one that changed?
Yes. Layout is a tree-wide computation — changing one element's size can push its siblings into new positions, change its parent's size if the parent sizes to its content, or ripple further depending on the layout mode in use (normal flow, flexbox, grid), so the browser may need to recompute layout well beyond just the element that changed.
After layout recalculates, does the browser have to repaint the entire page?
No — paint invalidation is generally limited to the regions or layers actually affected by the change, not the whole viewport. Modern browsers track which areas became 'dirty' and only repaint those, which is part of why isolating frequently-changing content to its own composited layer can help performance.
Is every DOM node represented in the render tree?
No. Nodes that never produce visible output — like <head>, <script>, <meta> — and any element explicitly set to display: none are excluded, since the render tree only needs to contain what will actually need a box on screen.
Why does the browser start rendering before the entire HTML file has finished downloading?
The HTML parser is incremental — it builds the DOM as bytes stream in rather than waiting for the whole document, so the browser can start constructing the render tree and painting an initial view of the page as soon as enough content and CSS have arrived, rather than leaving the user staring at a blank screen the whole time.
Why does splitting rendering into separate stages (render tree, layout, paint) make the browser more efficient at handling later changes?
Because each stage only has to rerun when something relevant to it changes. A pure color change can skip layout entirely and go straight to paint; a change confined to a composited layer's transform can skip both layout and paint. Collapsing everything into one step would mean every change, however small, forced the full pipeline to rerun.
A page animates an element's `margin-left` in one place and `transform: translateX()` in another, both moving it the same distance — which is likely to feel smoother, and why?
The transform: translateX() version, because it can typically run entirely on the compositor, skipping layout and paint on every frame. Animating margin-left changes the element's actual position in the layout, forcing a layout recalculation (and usually a repaint) on every single frame of the animation.
What's the more precise, spec-adjacent name for what's often casually called 'reflow,' and what's the equivalent for 'repaint'?
'Reflow' corresponds to the layout stage — recalculating size and position. 'Repaint' corresponds to the paint stage — filling in pixels based on the layout already computed. The casual and formal names refer to the same two stages; 'reflow'/'repaint' just describe them as things that happen again in response to a change, after the initial layout/paint.