Overview
"What happens when you type a URL" is a cliché interview question because it threads through everything: networking, parsing, layout, paint, and JavaScript's concurrency model. For a working engineer the payoff is diagnostic — jank, layout thrashing, blocked main threads, and hydration mismatches all become legible once the pipeline underneath is concrete rather than folklore.
Key points
-
From bytes to DOM: HTML parses incrementally into the DOM; CSS into the CSSOM;
<script>(withoutdefer/async) blocks parsing — the original reason for "scripts at the bottom" and the modern reason fordeferas default. -
The rendering pipeline: style → layout (geometry) → paint (pixels into layers) → composite (GPU assembles layers). Cost descends left to right:
widthtriggers layout+paint+composite;transform/opacitycomposite only — the entire basis of "animate transforms, not top/left".flowchart LR ST[Style] --> LY["Layout (geometry)"] LY --> PT["Paint (pixels into layers)"] PT --> CP["Composite (GPU assembles layers)"] W["Change width"] -->|"re-enters at layout: layout + paint + composite"| LY TR["Change transform or opacity"] -->|"composite only"| CP -
The event loop: one main thread runs JS, style, layout, and paint; tasks (macrotasks) run one per loop turn, then the entire microtask queue (promises) drains before rendering gets a chance — a long task or a microtask storm freezes the page at ~16ms/frame stakes.
requestAnimationFramebefore paint; workers for real off-thread compute.flowchart TB MT["Run one macrotask"] --> MQ["Drain the entire microtask queue (promises)"] MQ --> RAF["requestAnimationFrame callbacks"] RAF --> RD["Rendering opportunity: style, layout, paint"] RD -->|"next loop turn"| MT -
Layout thrashing: interleaving DOM writes with reads of layout-forcing properties (
offsetHeight…) forces synchronous reflow per iteration — batch reads, then writes; it's the classic self-inflicted performance bug. -
Process & security model: per-site processes (site isolation), the same-origin policy as the bedrock rule, CORS as its relaxation protocol (not a server shield), cookies vs. storage, and why the network tab shows preflights.
-
Modern rendering strategies sit on this: SSR streams HTML the parser can use immediately; hydration re-attaches JS to server-rendered DOM (mismatch bugs = the two renders disagreed); islands/server components shrink the JS that must hydrate at all.
-
To explore: V8's JIT tiers and hidden classes, speculative parsing & preload scanner,
content-visibility, the RAIL model as budgets — and web performance for the measurement side.
Practice
- Event-loop prediction katas (source) — write task/microtask/rAF snippets, predict the output order, then check in Loupe or DevTools; drills the loop until "why did this run first" stops being a mystery.
- Layout-thrashing lab (source) —
write a loop that interleaves DOM writes with
offsetHeightreads, watch the forced reflows in the Performance panel, then batch reads/writes and measure the difference. - Toy browser engine (source) — build a browser in Python following Web Browser Engineering; the whole pipeline, in code you wrote yourself.
Related
- Web performance — the metrics layer over this machinery.
- Networking fundamentals — the URL-to-bytes half of the journey.
- WebSockets & bidirectional protocols — EventSource and WebSocket live in this runtime.
- TypeScript deep dive — the language running on the main thread.
- Rendering strategies & meta-frameworks — the strategies feeding this pipeline their HTML.
- CSS architecture & layout — what style recalculation, layout, and paint actually execute.
- Hexagonal reference implementation — Frontend — the house architecture that treats this platform as the framework.