skip to content

In a page-load waterfall, what is a critical request chain, and why is a deep chain worse than the same number of bytes fetched in parallel?

level: seniorimportance: should knowfreq 40%

answer

  1. a diagonal, not a staircase
  2. each link is one round trip
  3. the browser cannot request what it has not seen
  4. @import and script-injected assets
  5. multiplexing does not help discovery

basics

~20 s

A critical request chain is a series of render-blocking requests where each one is discovered only after the previous response is parsed. Depth costs a round trip per link regardless of size, so the same bytes fetched in parallel arrive far sooner.

solid answer

~50 s

A critical request chain is a run of requests on the critical path where each link is *discovered* only by parsing the response before it — HTML reveals a stylesheet, the stylesheet's `@import` reveals a second stylesheet, that one's `url()` reveals a background image. Because discovery is serialized, so is the network: every link costs at least one round trip, plus connection setup if it introduces a new origin. Three 10 KB files chained cost three round trips; the same three fetched in parallel cost roughly one. That is why depth, not total weight, is usually what makes a mobile page feel broken — latency dominates. Multiplexing does not rescue you, because the browser cannot request something it has not yet learned about. The fixes all work by moving discovery earlier: flatten `@import` into build-time concatenation, reference the resource from the HTML rather than from inside another asset, or preload the late-discovered file.

code

css · 7 lines
css
/* app.css — the browser cannot see theme.css until this file has been parsed */
@import url("theme.css");

.hero {
  background: url("hero-bg.avif") no-repeat center / cover;
  min-height: 60vh;
}

go deeper

for a junior

Know that a browser can only request a file once it has seen a reference to it, and that a file referenced from inside another file is therefore fetched later than one referenced from the HTML.

for a middle

Explain the arithmetic — one round trip per link regardless of size — and name the common chain sources such as CSS @import and background images declared inside a stylesheet.

for a senior

Demonstrate reading a throttled waterfall by start times, attributing each link to what discovered it, and prescribing removals rather than reaching for a CDN or more parallelism.

for a principal

Recognise that most deep chains are organisational — an injected tag that injects more tags — so the durable fix is a rule about what may be script-injected on the critical path, not a one-off flattening.

## What a chain is Open a waterfall and the healthy pattern looks like a staircase with a single wide step: the HTML arrives, and then a cluster of requests all start at roughly the same moment. The unhealthy pattern is a diagonal — request B starts when A ends, C starts when B ends. That diagonal is a **critical request chain**, and its depth is measured in links, not bytes. Chains form because the browser can only request what it knows about, and it learns about resources by parsing the responses it already has: - HTML → `<link rel="stylesheet" href="app.css">` - `app.css` → `@import url("theme.css")` - `theme.css` → `background: url("hero.avif")` Four serialized network operations before the hero can be drawn. Each costs a full round trip minimum. At 150 ms RTT that is 600 ms of pure waiting, whatever the file sizes are. The same four resources referenced directly from the HTML would have been requested nearly simultaneously. ```css /* app.css — theme.css is invisible to the browser until this file is parsed */ @import url("theme.css"); .hero { background: url("hero-bg.avif") no-repeat center / cover; } ``` ## Why parallelism does not save you A common wrong answer is "HTTP/2 multiplexes, so this is a solved problem." Multiplexing removes the *connection* limit — it lets many requests share one connection without head-of-line blocking at the connection level. It does nothing about a dependency you have not discovered yet. There is no request to multiplex until the previous response has been parsed. Chain depth is a discovery problem, not a concurrency problem. The same reasoning disposes of "just put it on a CDN." An edge server shortens each round trip; it does not remove any of them. Four round trips at 30 ms is better than four at 150 ms, but it is still four, and the fix that removes three of them is worth more than the one that shortens all four. ## Where chains hide The textbook case is CSS `@import`, which is why bundling it away at build time is standard practice. But the more damaging chains in modern apps are built from things that look nothing like an import: - **Script-injected resources.** A tag manager loads, and *it* injects the font stylesheet, the experiment framework, and a second analytics script. Each level is one more round trip, and none of it is visible to the preload scanner because none of it exists in the HTML. - **Resources referenced from inside CSS.** A background image or a font file is discovered only after the stylesheet is parsed. Anything that must paint early should be discoverable from the markup. - **Data-driven URLs.** The HTML paints a shell, JavaScript fetches JSON, the JSON contains the image URL. Three levels deep before the main content can even be requested. - **Redirects.** Every hop is an extra round trip on the same conceptual link, and a redirect chain on the document itself delays absolutely everything. - **A late-discovered new origin.** Discovery plus DNS plus TCP plus TLS, which is why a chain that crosses to a third-party host costs far more than one that stays put. ## Diagnosing one Record a load with the cache disabled and network throttling applied, because on a fast local connection a chain compresses to something that looks fine. Then read the waterfall for *start times*, not durations: a request whose start aligns with the previous request's end is a chain link. Lighthouse reports the same structure directly as a tree of dependent requests, which is the fastest way to see the depth on a page you did not build. The question to ask at each link is always the same: **what taught the browser about this request, and could the HTML have taught it instead?** ## Fixing one In rough order of value: 1. **Remove the link.** Flatten `@import` at build time so both stylesheets ship as one file. Reference a critical background image from the markup rather than from CSS. Inline the tiny thing that is causing a whole round trip. 2. **Move discovery earlier.** If a resource genuinely must live inside another asset, declare it in the HTML head so the preload scanner starts it immediately instead of waiting for the parent to parse. 3. **Warm the connection.** When a link crosses to another origin and cannot be removed, establishing the connection early converts several round trips into one. 4. **Attack the head of the chain.** A slow TTFB on the document delays every downstream discovery; reducing it shortens the whole chain at once. ## The framing that lands in an interview Say explicitly that a chain is about **discovery order**, quantify it in round trips rather than kilobytes, note that multiplexing and CDNs shorten links but never remove them, and then give a concrete removal — flattening an `@import`, or hoisting a script-injected font reference into the HTML.

  • Why is a chain that crosses to a new origin more expensive than one that stays on the same host?
    Because the link costs more than a round trip. A new origin means a DNS lookup, a TCP handshake and a TLS negotiation before the request is even sent — several round trips instead of one. Keeping critical resources on the document's origin, or warming the connection early, is often worth more than shrinking the file.
  • A team says their CDN made the chain irrelevant. How do you respond?
    A CDN shortens each round trip by moving the server closer; it does not remove any of them. A four-deep chain is still four sequential waits, just shorter ones. The measurement that settles it is the waterfall on a throttled connection: if request start times still line up with the previous request's end, the depth is unchanged.
  • How do you find chains created by a third-party tag manager?
    Record a throttled load and follow the initiator column: requests initiated by a script rather than by the parser are the injected ones, and each level of initiation is a level of depth. That view also shows how many origins the chain touches, which is usually the real cost.

It is a relay race where each runner is only handed the name of the next runner as they cross the line. Nobody can start warming up early — the total time is the sum of the legs, no matter how fast any individual runner is.

saying these in an interview costs you the question

  • Measures the chain in kilobytes rather than round trips
  • Claims HTTP/2 multiplexing removes serialized discovery
  • Says a CDN eliminates the chain
  • Ignores script-injected requests because they are not in the HTML
  • Tests only on a fast connection where the chain looks fine

context