DNS, HTTP, browser request lifecycle, and QUIC
You typed a URL and hit enter. Before a single byte of your backend code runs, the network has already spent a budget of round trips — and that budget, not your server, is usually what makes a page feel slow.
0. The interview hinge
The canonical version of this question is blunt: "You typed a URL and the page is slow. Walk the stack and tell me where the time goes." A weak answer recites "DNS, then TCP, then the server sends HTML." A senior answer quantifies each hop, identifies which hops are serial RTTs you can collapse, and names the one place the time actually pools (usually the front-end render, not the network). The hinge is therefore: account for the round trips, then attack them.
1. Clarify the contract
This is a "walk the stack" case, so the contract is an explanation contract, not a CRUD API. The candidate must be able to:
- Trace the request path from URL bar to painted pixels, naming every actor (browser cache, OS resolver, recursive resolver, authoritative DNS, CDN, origin).
- Put a round-trip cost on each stage and show what caching / QUIC / 0-RTT removes.
- Compare HTTP/1.1 vs HTTP/2 vs HTTP/3 at the level of head-of-line blocking, not feature bullets.
- Explain the DNS TTL trade-off (failover speed vs. cache hit rate) as a staleness/replication decision.
- Say where page-load time actually goes once the bytes arrive — and it is rarely the network.
What it need not do: design the origin application, choose a database, or solve auth (that is C40). The scope ends at "first byte arrives and the page renders."
2. Put numbers on the round-trip budget
The load-bearing calculation for this entire case is the RTT budget before first byte. Assume a representative RTT = 50 ms to a regional server, a cold cache, and a modern stack (TCP + TLS 1.3). Each handshake is a serial dependency, so the costs add:
| Stage | Round trips (cold) | Time @ 50ms RTT | What it does |
|---|---|---|---|
| DNS resolution | 1 RTT (0 if cached) | 50 ms | Name → IP. Recursive resolver may itself need several queries on a true miss; one RTT to the resolver is the optimistic figure. |
| TCP handshake | 1 RTT | 50 ms | SYN → SYN-ACK → ACK; the client may send data with the final ACK, so it counts as 1 RTT before it can send. |
| TLS 1.3 handshake | 1 RTT | 50 ms | ClientHello+keyshare → ServerHello+cert+Finished. (TLS 1.2 was 2 RTT — TLS 1.3 already cut one.) |
| HTTP request → first byte | 1 RTT | 50 ms | GET → server processes → first response byte (TTFB). |
| Total to first byte (cold) | 4 RTT | ~200 ms | Pure network/handshake tax, before any HTML is parsed. |
Now toggle the optimisations and watch the budget collapse. Each row removes serial round trips from the critical path:
| Scenario | DNS | TCP/conn | TLS | Request | RTT total | @ 50ms |
|---|---|---|---|---|---|---|
| Cold, TCP+TLS1.3 | 1 | 1 | 1 | 1 | 4 | ~200 ms |
| DNS cached | 0 | 1 | 1 | 1 | 3 | ~150 ms |
| QUIC (1-RTT handshake) | 0 | 1 (combined transport+crypto) | 1 | 2 | ~100 ms | |
| QUIC 0-RTT (resumed) | 0 | 0 (resumption ticket) | 1* | 1 | ~50 ms | |
| CDN hit, edge nearby (RTT≈10ms) | 0 | 1 | 1 | 2 | ~20 ms | |
*0-RTT sends the GET with the first crypto flight, so the request RTT and the handshake overlap — the data is in flight on the very first packet. Caveat: 0-RTT early data is replayable, so it is only safe for idempotent requests (a GET, never a "transfer $100").
The headline: QUIC plus 0-RTT collapses connection setup from 3 RTT to 0, taking the pre-request tax from ~150 ms down to ~0. At 50 ms RTT that is a 150 ms win on every cold connection — and on a mobile link at 150 ms RTT the same 3 saved round trips are worth 3 · 150 = 450 ms, which is the difference between "instant" and "sluggish." This is why the round-trip count, not bandwidth, is the number to attack first. Bandwidth helps the transfer; round trips dominate the setup, and for a small page the setup is most of the latency. See foundation lesson 02 for why latency and throughput are independent axes and why percentiles (p99) — not the mean — are the number that bites here (DDIA Ch. 1).
3. Define the surface area
The "API" here is the set of protocol exchanges on the path. Each is a question/answer that costs latency and can fail independently.
| Exchange | Why it exists / what it costs |
|---|---|
DNS query A/AAAA | Resolve hostname to IP(s). 0 RTT cached, 1+ RTT cold. Cacheable per TTL. |
TCP/QUIC connect | Establish reliable transport + (for QUIC) crypto in one. 1 RTT, or 0 on resumption. |
TLS handshake | Negotiate cipher, exchange keys, verify cert chain. 1 RTT in TLS 1.3 (folded into QUIC). |
HTTP GET + validators | The actual request, carrying cookies and cache validators (If-None-Match/If-Modified-Since). 1 RTT to TTFB; can return 304 Not Modified with no body. |
4. Model the data
The "data" on this path is the set of cacheable artifacts and the metadata that governs their staleness. Each lives at a different layer with a different TTL, and getting the TTL wrong is the single most common production bug here.
The mental model: a DNS record cached for its TTL is a replica with a staleness bound — exactly DDIA Ch. 5's replication/TTL trade. A long TTL means more clients read a stale (possibly dead) IP after you fail over; a short TTL means more cache misses and more resolver load. The cache headers (Cache-Control: max-age, ETag) play the same role for assets one layer up. The whole case is really "how stale can each replica be, and what does staleness cost when something changes?"
5. Linearized design — follow one request
Walk the request in the order it actually moves. The bottleneck — serial round trips — falls out naturally.
- Resolve the name. The browser checks its own cache, then the OS cache, then asks the configured recursive resolver. On a true miss the resolver walks root → TLD → authoritative — but each of those is itself cached, so the common case is a single RTT (or zero). This is the first place a round trip either disappears (cached) or stalls the whole load (cold + slow resolver).
- Open the connection. TCP three-way handshake (1 RTT), or QUIC's combined transport+crypto handshake (1 RTT, or 0 on resumption) directly over UDP.
- Secure it. TLS 1.3 (1 RTT) for TCP; folded into the QUIC handshake so there is no separate cost.
- Send the HTTP request. GET with cookies, headers, and cache validators. If the client holds a valid
ETagand the resource is unchanged, the origin returns304with no body — a full RTT spent but a body transfer saved. - Route it. The request lands at the nearest CDN edge / load balancer first. A CDN hit answers from an edge ~10 ms away and the origin never wakes up; a miss proxies back to the origin application server. The CDN is the cheapest RTT reducer because it shrinks every RTT on the path, not just one stage.
- Return HTML + assets with a cache policy. The origin sets
Cache-Control/ETagso the next visit can skip steps 4–6 entirely. - Render. The browser parses HTML, discovers subresources (CSS, JS, images, fonts), opens further fetches, runs JavaScript, and paints. This is where the second latency budget lives — see the deep dive on where page-load time really goes.
6. Deep dives
(1) HTTP/2 TCP head-of-line blocking vs HTTP/3/QUIC per-stream delivery
HTTP/1.1 forced one request per connection at a time, so browsers opened ~6 parallel TCP connections per host to fake concurrency — 6× the handshakes. HTTP/2 fixed the application layer: it multiplexes many logical streams over a single TCP connection, so one handshake serves the whole page. But it left a subtler problem unsolved. TCP delivers a single, strictly ordered byte stream. If one packet is lost, TCP holds back every byte that arrived after it — across all streams — until the retransmission arrives, because TCP cannot know those later bytes belong to a different, independent HTTP/2 stream. So a single lost packet for the logo image stalls the CSS, the JS, and everything else multiplexed on that connection. That is TCP head-of-line blocking, and on a lossy mobile link it can erase HTTP/2's multiplexing win entirely (DDIA Ch. 8: networks are unreliable; loss and reordering are the normal case, not the exception).
HTTP/3 runs over QUIC, which is built on UDP and implements its own per-stream reliability. QUIC knows the stream boundaries, so a lost packet only stalls its own stream; the other streams keep delivering. The head-of-line blocking moves from "whole connection" down to "one stream," which is exactly where you want it. QUIC also folds the transport and TLS handshakes into one (the 1-RTT setup above), supports 0-RTT resumption, and — because the connection ID is independent of the IP/port 4-tuple — survives a network change (Wi-Fi → cellular) without a new handshake.
(2) Long vs short DNS TTL — failover speed vs cache hit rate
A DNS record's TTL is the staleness bound on a replicated value (DDIA Ch. 5). Set it long (say 24 h) and the vast majority of lookups hit a cache — zero DNS RTT, low resolver load, fast. But the cost is brutal on the day you need to move traffic: if a data centre or a load balancer IP dies, every client and resolver that cached the old IP keeps hitting the dead address until its TTL expires. A 24 h TTL means up to 24 h of clients pointed at a corpse. Set it short (say 30–60 s) and failover is near-instant — clients re-resolve within a minute and follow you to the new IP — but you pay with cache misses and a constant stream of DNS queries (more resolver load, an added ~1 RTT more often).
The senior move is to match TTL to volatility: long TTL for stable, anycast-fronted endpoints that rarely move; short TTL for the records you steer for failover or blue/green cutover. In practice you keep failover-critical records at 30–60 s and lean on anycast (one IP advertised from many sites, BGP routes you to the nearest live one) so that failover happens in the routing layer and does not depend on DNS TTL expiry at all. This is the same replication/staleness tension that foundation lesson 06 covers for asset caching — and the defer/cache mechanics for fingerprinted assets are taught there, so I won't re-derive them.
(3) Where page-load time actually goes
Here is the answer that separates seniors: for most real pages, the network handshake is not where the time goes — the front-end is. The ~200 ms cold network tax is real and worth attacking, but once the HTML arrives the browser must parse it, discover and fetch dozens of subresources (often the true bottleneck: 50+ requests, each needing a connection or a CDN round trip), parse and execute JavaScript on the main thread, build the render tree, and paint. A typical breakdown for a content page:
| Phase | Typical share of "feels loaded" | Lever |
|---|---|---|
| DNS + connection setup | ~100–200 ms | QUIC, 0-RTT, DNS cache, CDN edge |
| TTFB (origin compute) | ~50–300 ms | Cache at CDN, faster backend, SSR |
| Subresource fetch (CSS/JS/img) | ~300–800 ms | Fewer/smaller assets, HTTP/2-3 multiplexing, preload, compression |
| JS parse + execute + render | ~200–1000 ms+ | Less JS, code-splitting, defer/async, server-render |
So when an interviewer says "the page is slow," the disciplined walk is: (1) is DNS cold or the resolver slow? (2) is this a fresh connection paying 3 setup RTTs we could collapse with QUIC/keep-alive? (3) is TTFB high — i.e. is the origin slow, fixable with a CDN cache hit? (4) or — most often — is the network fine and the page is shipping 2 MB of render-blocking JavaScript? You cannot know without splitting the budget; the failure of the weak candidate is assuming the slowness is "the server."
7. Trade-offs
| Choice | Buys | Costs | Choose when |
|---|---|---|---|
| Long DNS TTL vs short TTL | High cache hit, low resolver load, fewer RTTs | Slow failover — stale clients hit a dead IP for up to the TTL | Stable, anycast-fronted endpoints that rarely move |
| HTTP/3 (QUIC) vs HTTP/2 (TCP) | Per-stream delivery (no TCP HoL), 1-RTT/0-RTT setup, connection migration | UDP sometimes blocked/throttled by middleboxes; younger stack | Lossy / mobile networks; you control client and server; always with a TCP fallback |
| 0-RTT resumption vs always full handshake | Saves a full RTT on reconnect | Early data is replayable — unsafe for non-idempotent requests | Idempotent GETs to your own QUIC endpoints |
| Aggressive asset cache vs quick deploy | Fast repeat visits, near-zero origin load | Clients can run stale code after a deploy | Fingerprinted (content-hashed) filenames make this safe |
| Redirects vs direct canonical URL | Flexible routing / migration | Each redirect adds a full DNS+conn+RTT round trip | Temporary migration windows only — never on the hot path |
The sharpest trade is HTTP/3 vs HTTP/2, because the "obvious" answer (newer is better) is incomplete. QUIC's per-stream delivery is a clear win on lossy links — but it rides UDP, and some corporate firewalls and carrier middleboxes throttle or block UDP, or simply haven't been tuned for QUIC's higher packet rates. The correct posture is not "switch everything to HTTP/3"; it is "advertise HTTP/3 via Alt-Svc, let capable clients upgrade, and keep HTTP/2-over-TCP as the fallback for everyone else." You get QUIC's win where the network allows it and lose nothing where it doesn't.
8. Failure modes
| Failure | Why it bites | Mitigation |
|---|---|---|
| DNS provider outage | Series-circuit link 1 fails → entire site unreachable, regardless of origin health | Multiple authoritative providers, low TTL on failover records, tested failover |
| Certificate expiry | TLS handshake fails for every client at once — a self-inflicted total outage | Automated renewal (ACME), expiry monitoring + alert well before the date |
| Redirect loop | Each hop is a full round trip; a loop burns RTTs and never loads | Cap redirect depth, test canonicalization rules |
| Stale DNS after failover | Long TTL keeps clients pinned to the dead IP | Short TTL on steered records + anycast so routing fails over independent of TTL |
| Cache serves bad / old asset | Aggressive cache + reused filename → clients run broken old JS after deploy | Content-hashed (fingerprinted) filenames; rollback-safe deploys |
| QUIC/UDP blocked by middlebox | Connection silently stalls if there's no fallback | Advertise via Alt-Svc; always retain HTTP/2-over-TCP fallback |
9. Interview prompts you should be ready for
- "You typed a URL and the page is slow — walk the stack and tell me where the time goes." (senior answer) Split the budget. Cold network tax is ~4 RTT ≈ 200 ms at 50 ms RTT: DNS (1, or 0 cached), TCP (1), TLS 1.3 (1), HTTP→TTFB (1). Then ask: is the connection fresh (3 collapsible setup RTTs) or reused? Is TTFB high (slow origin — put a CDN cache in front)? But most often the network is fine and the time is in the front end — 50+ subresource fetches and megabytes of render-blocking JS parsing on the main thread. Name the phase before prescribing a fix.
- Why does HTTP/2 still suffer head-of-line blocking when it multiplexes? (senior answer) The multiplexing is at the HTTP layer, but it still rides a single TCP connection, and TCP delivers one strictly-ordered byte stream. One lost packet makes TCP hold back every later byte across all streams until the retransmit lands. HTTP/3 fixes it by running over QUIC, which tracks stream boundaries itself so loss only stalls its own stream.
- What exactly does QUIC/0-RTT save, in round trips? (senior answer) A fresh QUIC connection folds transport+crypto into one 1-RTT handshake (vs TCP's 1 + TLS 1.3's 1 = 2). On resumption, 0-RTT sends the GET in the very first packet — 0 setup RTTs. So worst-case you go from 3 setup RTTs to 1, best-case to 0; at 50 ms that's 100–150 ms saved per cold connection, and proportionally more on mobile. Caveat: 0-RTT early data is replayable, so only for idempotent requests.
- How do you choose a DNS TTL? (senior answer) It's a staleness-vs-load trade. Long TTL maximises cache hits and minimises resolver load but pins clients to a dead IP for up to the TTL on failover. Short TTL gives near-instant failover at the cost of more misses/queries. Keep failover-critical records at 30–60 s, stable records long, and lean on anycast so failover happens in routing, independent of TTL expiry.
- A user's first visit is fast but they complain the site is "slow" — what's likely? (senior answer) If repeat visits feel slow, suspect cache misconfiguration (no
Cache-Control/ETag, so every asset re-downloads). If the first visit is slow, suspect cold-connection RTTs or a slow origin TTFB. Always confirm whether the bottleneck is network setup, origin compute, or front-end render before changing anything. - Why not just put a CDN in front and call it done? (senior answer) A CDN is the highest-leverage move — it shrinks every RTT (the edge is ~10 ms away) and absorbs the origin load for cacheable content. But it only helps cacheable, mostly-static responses; personalised/dynamic responses still go to origin, and you've added a cache-invalidation problem (fingerprint assets, set sane TTLs). It reduces the network tax dramatically but does nothing for a heavy-JS front end.
- Should you turn on HTTP/3 everywhere? (senior answer) No — advertise it via
Alt-Svcand let capable clients upgrade, but keep HTTP/2-over-TCP as fallback. QUIC rides UDP, which some middleboxes throttle or block; without a fallback those clients stall. You want the win where the network allows it and zero regression where it doesn't. - Where does this connect to load and capacity? (senior answer) The same queueing math from lesson 02 applies at the edge: as a CDN node or resolver nears saturation (ρ→1) tail latency blows up superlinearly, and that p99 is what users feel (DDIA Ch. 1). The network-unreliability assumptions — loss, reordering, partition — are DDIA Ch. 8, and they're exactly what TCP HoL blocking and QUIC's per-stream recovery are reacting to.
Related foundation lessons
Three foundation lessons carry the load here, and I cite rather than re-derive them. Lesson 02 (latency & throughput) owns the RTT/latency math and the percentile argument — the entire round-trip budget above is an application of it. Lesson 06 (caching) owns the hit-rate→origin-load and TTL/staleness mechanics; the asset-cache and defer mechanics live there, so this case covers only the DNS-TTL and CDN-edge deltas. Lesson 15 (rate limiting & the edge) is the companion for what else happens at that same edge — TLS termination, admission control, and why the front door is the cheapest place to enforce cross-cutting concerns.