What is a web page's critical rendering path, and what does it mean for a resource to be render-blocking?
answer
- blank screen until CSS arrives
- DOM plus CSSOM makes the render tree
- stylesheets and sync scripts, not images
- count blocking bytes and round-trip depth
basics
~20 sThe critical rendering path is everything a browser must fetch and process before it can paint: the HTML, the DOM built from it, and the CSS. A render-blocking resource is one the browser insists on having before that first paint.
solid answer
~50 sThe critical rendering path is the chain of work between the first byte of HTML arriving and the first pixels appearing. The browser parses HTML into the DOM, fetches and parses the CSS it finds into the CSSOM, combines the two into a render tree, then runs layout and paint. Anything it refuses to paint without is **render-blocking** — in practice external stylesheets, plus synchronous scripts, which additionally stop the HTML parser. Images, fonts and deferred scripts are *not* render-blocking: the page can paint without them. Optimising the path means reducing three things — how many blocking resources sit on it, how many bytes each costs, and how many round trips deep the chain is. That is why the standing advice is to keep the CSS small and discovered early, and to keep blocking scripts out of the `<head>`.
go deeper
Be able to say plainly that stylesheets and synchronous scripts hold up the first paint while images and deferred scripts do not, and that the browser needs both the DOM and the CSS before it can draw.
Walk the sequence out loud — HTML to DOM, CSS to CSSOM, render tree, layout, paint — and explain why a partially-loaded stylesheet makes painting unsafe rather than merely ugly.
Show that you can identify the real path from a waterfall on a throttled connection and attribute a slow first paint to a specific blocking resource, rather than reciting generic advice.
Frame the path as a budget the whole organisation spends: every team that adds a tag to the head is buying delay for every page, so the interesting question is who is allowed to put things there and how that is enforced.
## What the browser must do before it can paint A blank white screen is not the browser being lazy — it is the browser waiting for the inputs it needs to paint anything at all. Those inputs form a fixed sequence, and that sequence is what people mean by the *critical rendering path*. 1. **HTML arrives** and the parser starts turning bytes into a **DOM** — a tree of element nodes. 2. As it parses, it encounters references to other files: stylesheets, scripts, images. 3. **CSS is fetched and parsed into the CSSOM** — a parallel tree describing the computed style rules. 4. DOM and CSSOM are combined into a **render tree**: only the nodes that will actually be drawn, each with its resolved styles. 5. **Layout** computes the geometry of every box; **paint** fills in pixels. The crucial point is step 4. The browser cannot build a render tree from the DOM alone, because it does not yet know whether a given element is `display: none`, or red, or 400 pixels tall. So while a stylesheet the page has already referenced is still in flight, there is nothing safe to paint. Paint the DOM early and every user sees an unstyled flash, then a violent reflow — so browsers hold the paint instead. ## Render-blocking versus everything else A resource is **render-blocking** when the browser will not produce a first paint until that resource has been fetched *and* processed. In everyday pages that list is short: - **External stylesheets** referenced from the document — the canonical render-blocking resource. - **Synchronous scripts** (`<script src="...">` with no `defer` or `async`) — these are worse, because they also halt HTML parsing at the point they appear, so no further DOM is built until they have downloaded and executed. And what is *not* on the path is just as important to be able to name: - **Images** — the page paints without them; a missing image leaves a gap, it does not delay the paint. - **Web fonts** — text can render in a fallback face (or be briefly invisible) while the font loads. - **Deferred and async scripts** — they do not hold up the first paint. - **Anything fetched by JavaScript after load** — by definition it happens after the paint you care about. A useful sanity check: open the network waterfall, find the moment of first paint, and ask which requests started *and finished* before it. Those are your critical path. Everything else is noise for this particular question. ```html <head> <link rel="stylesheet" href="/app.css"> <!-- blocks the first paint --> <script src="/analytics.js"></script> <!-- blocks the parser AND the paint --> <script src="/app.js" defer></script> <!-- neither --> </head> <body> <img src="/hero.jpg" alt=""> <!-- neither --> </body> ``` ## The three levers Once you can see the path, there are only three things you can do to it, and being explicit about which one you are pulling is what separates a real answer from advice-by-slogan. **Fewer resources on the path.** Every blocking file is at minimum one extra round trip after its discovery. Merging two stylesheets, or removing a blocking third-party script from the head, removes a whole link. **Fewer bytes per resource.** A stylesheet carrying rules for every page on the site is paid for on the first paint of every page. Compression, minification and removing dead rules all shorten the same wait. **Fewer round trips of depth.** This is the one people miss. Ten resources requested at once cost roughly one round trip; three resources where each is discovered only after the previous one is parsed cost three. Depth, not count, is usually what makes a slow path pathological on a high-latency mobile connection. ## Why this framing pays off in an interview Interviewers ask this because it converts vague performance folklore into a causal model. "The page is slow" becomes "first paint waits on a 180 KB stylesheet that is discovered late, and a synchronous tag manager script in the head that also stops the parser." The metric that marks the end of this path is First Contentful Paint, and a poor Largest Contentful Paint very often has the same root cause, because the hero element cannot be painted until the path completes either. A final nuance worth having ready: the parser does not go completely idle while a blocking resource loads. Browsers run a **preload scanner** that reads ahead in the raw HTML for further `src`/`href` references and starts those downloads in parallel. That is why a stylesheet in the head does not serialize the rest of the page's downloads — and it is also why resources that are *not* visible in the raw HTML (injected by script, or referenced from inside a CSS file) are discovered late and hurt disproportionately.
- If images are not render-blocking, why does a big hero image still show up as a performance problem?Because it is usually the Largest Contentful Paint element. The page can paint around it, but the metric that represents "the main content is visible" is not satisfied until it decodes. That makes it a *content* problem rather than a critical-path problem — the fix is discovery and priority for that one image, not removing blocking work.
- Does the HTML parser stop completely while a render-blocking stylesheet downloads?No. The parser keeps building the DOM, and a preload scanner reads ahead in the raw markup to start other downloads in parallel. What is withheld is the *paint*, not the parse. The exception is a synchronous script, which genuinely halts the parser at its tag until it has run.
- How would you show a colleague which resources are actually on the critical path of a page?Record a load in the network panel with cache disabled and throttling on, mark the First Contentful Paint line, and list the requests that both started and completed before it. Anything after that line is off the path for first paint. A Lighthouse run reports the same set as render-blocking resources.
saying these in an interview costs you the question
- Says images and fonts block the first paint
- Claims the browser paints the DOM before any CSS loads
- Thinks the path ends at DOMContentLoaded or the load event
- Believes moving scripts to the body end makes CSS non-blocking
- Treats resource count as the only cost and ignores chain depth