Rendering Lifecycle
The browser rendering pipeline is a fixed sequence, but a real application is not static. An app changes state, updates the DOM, and those changes have to flow through the pipeline into a new frame on screen, all while staying within a deadline. Understanding that flow, from state change through layout and paint to the displayed frame, is what lets you recognize where your code is expensive and what to fix.
This guide builds on the browser rendering pipeline to show how it works in the lifecycle of an interactive app, and connects to the performance budgets that make the whole thing sustainable.
What you'll learn
The lifecycle flow
The lifecycle is a chain: state changes trigger the next stage, which triggers the next, until a frame is painted and composited to the screen. Understanding that chain is what makes performance bottlenecks visible.
From change to frame
Your code changes state, which modifies the DOM or styles. The browser detects those changes and recalculates styles for all affected elements, a process that may cascade through the tree. If geometry changed, layout runs. If styles changed, paint runs. Finally, all the painted pieces are composited into the frame you see. Every stage flows into the next, so a change at an early stage like layout forces everything after it to run again, while a change that skips to compositing, like a transform, may run only compositing.
What triggers each stage
Different DOM changes trigger different stages, and knowing which trigger what is the key to avoiding unnecessary work.
Reading and writing in the right order
// Bad: layout thrashing
for (let i = 0; i < items.length; i++) {
items[i].style.width = items[i].offsetWidth * 2 + 'px';
}
// Good: batch reads, then writes
var widths = items.map(el => el.offsetWidth * 2);
items.forEach((el, i) => el.style.width = widths[i] + 'px');
Reading offsetWidth forces layout, and reading it in a loop where you are also writing styles forces layout to run once per iteration, a catastrophic pattern called layout thrashing. The fix is to batch: collect all reads in one phase while the browser has already-computed geometry in hand, then write them all at once, so layout runs once instead of repeatedly. A tool like FastDOM enforces this discipline.
Layout thrashing and batching
Layout thrashing is easy to create and catastrophic in its cost. It is also easy to prevent with attention to the order of operations.
Separating reads from writes
The pattern is always the same: read all DOM measurements at once, then change styles, then let the browser run layout once. If you interleave reads and writes, the browser has to keep rerunning layout to answer your read, and the frame budget evaporates. Backbone views that batch updates, or any framework that does reactive updates at the right granule, prevent this naturally, but raw DOM manipulation is easy to get wrong.
The frame budget
All of the lifecycle, style through compositing, has to fit in about sixteen milliseconds to maintain sixty frames per second. That budget is tight, and exceeding it is where jank appears.
Fitting work into sixteen milliseconds
Sixty frames per second means a frame every 16.67ms. The browser itself consumes time for rendering, so your JavaScript and the rendering work together have maybe thirteen to fourteen milliseconds before the next input event should be handled. Exceed that and the frame misses its deadline, the display does not update, and the page feels sluggish. This is why moving expensive work off the main thread, using requestAnimationFrame to align updates, and being conscious of what each DOM change costs are not optional optimizations but foundational design decisions.
Observing the lifecycle
The lifecycle is invisible unless you have tools to see it. Fortunately the browser provides several.
DevTools and performance observability
Chrome DevTools and similar tools let you record a timeline showing when style, layout, paint, and compositing happen. Jank often appears as layout or paint taking longer than the frame budget allows. Measuring is essential: your intuition about what is slow is usually wrong, so capture what the browser is actually doing before optimizing.
Optimizing within the lifecycle
With the lifecycle mapped, optimization becomes a set of specific moves rather than vague performance advice. Every rule threads back to the pipeline.
A taxonomy of optimizations
Avoid layout by animating transform or opacity. Reduce paint area by breaking into layers. Skip layout altogether for properties the compositor can handle. Batch DOM reads and writes separately. Use requestAnimationFrame to stay in sync with the refresh rate. Move computationally expensive work to a web worker. These are not magic tricks; they are all direct consequences of how the rendering pipeline works and where the budget lives. The next guide zooms back out to examine different rendering strategies, how the DOM is created, and which approach makes a page fast.
Frequently Asked Questions
What is the rendering lifecycle?
It is the sequence from a state change to the frame appearing on screen: the state changes trigger style recalculation, which triggers layout, which triggers paint and compositing, which becomes the visible frame. Every stage flows into the next.
What is layout thrashing?
Reading the DOM after writing to it, or writing and reading in a loop, forces the browser to run layout multiple times per frame. For example, reading offsetWidth in a loop while changing styles thrashes layout and wastes the frame budget.
How do I avoid triggering layout unnecessarily?
Cache DOM measurements before loops, batch style writes and reads separately, and prefer transform and opacity which skip layout. Tools like FastDOM batch operations and read them in one phase, write in another, to prevent layout thrashing.
Why is the frame budget about 16 milliseconds?
Most screens refresh 60 times per second, which is one frame every 16.67 milliseconds. The browser must complete all its work in that time to show the frame and respond to the next user input, so exceeding the budget causes jank.
Read next: the Rendering hub for the full cluster, or revisit the browser rendering pipeline for the mechanics underneath.
Want to see rendering from the app side?
The complete Backbone guide builds views that flow efficiently through this lifecycle.
Explore the Backbone Guide →