Topics
v6.0.0
On this page · sections

URL to Pixels

From pressing Enter to seeing content, the browser resolves DNS, opens TCP/TLS, fetches HTML, parses DOM/CSSOM, and paints — each phase has measurable cost and optimization levers.

PhaseTypical cold costKey lever
DNS20–120msCache, dns-prefetch
TCP + TLS2–3 RTTpreconnect, session resume
TTFBServer + networkCDN, edge cache
DownloadBytes on wireCompression, HTTP/2/3
Parse + renderCSS + sync JSCritical CSS, defer JS
LCPHero resourcePreload, SSR, fetchpriority

DNS Lookup

Definition

The browser parses the URL, checks HSTS preload, then resolves hostname → IP through layered caches:

  1. Browser DNS cache
  2. OS cache
  3. Router/ISP resolver
  4. Authoritative nameserver
// Warm DNS for third-party origin const link = document.createElement('link'); link.rel = 'dns-prefetch'; link.href = 'https://api.example.com'; document.head.appendChild(link); window.addEventListener('load', () => { const [nav] = performance.getEntriesByType('navigation'); if (nav) { console.log('DNS ms:', nav.domainLookupEnd - nav.domainLookupStart); console.log('TCP ms:', nav.connectEnd - nav.connectStart); } });

TTL controls cache lifetime. CNAME chains add latency. dns-prefetch resolves only; preconnect does DNS + TCP + TLS.

Interview: DNS happens before TCP — resolve hostname, then connect to IP.

TCP and TLS

Definition

TCP: SYN, SYN-ACK, ACK = 1 RTT (more with packet loss).

TLS 1.3: 1 RTT after TCP. TLS 1.2 full handshake: 2 RTT. 0-RTT resumption is fast but replay-sensitive — disable for non-idempotent APIs.

// <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin> function measureConnectionPhases() { const [nav] = performance.getEntriesByType('navigation'); if (!nav) return; console.log({ tcpMs: nav.connectEnd - nav.connectStart, tlsMs: nav.requestStart - nav.secureConnectionStart, ttfbMs: nav.responseStart - nav.requestStart, }); } // Same origin + keep-alive reuses connection fetch('https://api.example.com/a').then(() => fetch('https://api.example.com/b') );

On 100ms RTT mobile, cold HTTPS can burn 400ms+ before first byte. Preconnect, session resumption, and HTTP/2/3 connection reuse amortize this.

Interview: Cold HTTPS ≈ 3–4 RTTs before application data. Preconnect saves DNS + TCP + TLS on critical origins.

HTTP/2 and Resource Discovery

Definition

HTTP/1.1: ~6 parallel connections per origin, head-of-line blocking per connection.

HTTP/2: one TCP connection, many multiplexed streams — no HTTP-level HOL (TCP HOL remains).

HTTP/3/QUIC: UDP-based, independent streams, faster loss recovery.

Browser discovers subresources from parsed HTML, CSS (@import, url()), and Link response headers. Early preload beats late discovery.

window.addEventListener('load', () => { const resources = performance.getEntriesByType('resource'); resources.forEach((r) => { console.log(r.name, r.nextHopProtocol); // h2, h3, http/1.1 }); }); // Server early hints (103) // Link: </style.css>; rel=preload; as=style // Link: </hero.webp>; rel=preload; as=image; fetchpriority=high

Sharding assets across subdomains hurts on H2/H3 — was an HTTP/1.1 hack. Server Push is largely deprecated; prefer preload hints.

Interview: H2 multiplexes streams on one connection but doesn’t remove main-thread parse blocking or TCP-level HOL.

Parse, DOM, and First Paint

Definition

HTML bytes → tokenizer → DOM tree (incremental). CSS fetched in parallel → CSSOM → blocks first render.

Render tree → layout → paint → composite → pixels. FCP = first text/image. LCP = largest content element.

const fcpObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.name === 'first-contentful-paint') { console.log('FCP ms:', entry.startTime); } } }); fcpObserver.observe({ type: 'paint', buffered: true }); const lcpObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); const last = entries[entries.length - 1]; console.log('LCP:', last.element?.tagName, 'at', last.startTime); }); lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });

Sync script without defer/async pauses HTML parsing. TTFB is server/network; element render delay is often client parse + CSS + JS.

Interview: TTFB ≠ FCP ≠ LCP. Split metrics when optimizing — attack the largest bucket first.

Critical Path Optimization

Definition

Audit with Navigation Timing + LCP breakdown. Typical wins:

  1. Edge cache HTML — cut TTFB
  2. Preconnect to API and font CDN
  3. Inline critical CSS, defer rest
  4. Preload LCP image with fetchpriority="high"
  5. Avoid sync third-party in <head>
  6. Code-split JS — route bundle not in critical path
  7. SSR/SSG hero so LCP element is in first HTML chunk
function criticalPathReport() { const nav = performance.getEntriesByType('navigation')[0]; if (!nav) return null; return { dns: nav.domainLookupEnd - nav.domainLookupStart, tcp: nav.connectEnd - nav.connectStart, tls: nav.requestStart - nav.secureConnectionStart, ttfb: nav.responseStart - nav.requestStart, download: nav.responseEnd - nav.responseStart, domInteractive: nav.domInteractive - nav.responseEnd, }; } // Anti-pattern — LCP waits on JS + fetch + image async function badHero() { const res = await fetch('/api/hero-config'); const { imageUrl } = await res.json(); document.querySelector('#hero img').src = imageUrl; }

If TTFB is fine but LCP slow → client render delay — fix CSS blocking, lazy hydration, or client-only hero fetch.

Service workers cache shells for repeat visits; navigation preload reduces SW boot penalty on first nav.

Interview: Put hero in HTML; preconnect known origins; defer everything else. Don’t preload all JS routes — bandwidth contention.

Senior Interview Summary

Mental model

URL Enter → DNS → TCP → TLS → HTTP → TTFB → Download HTML/CSS → Parse DOM/CSSOM → Layout → Paint → LCP

Practical rules

  • Measure each phase with Navigation Timing API
  • preconnect > dns-prefetch for origins hit immediately
  • Hero in SSR HTML, not client fetch
  • Monitor nextHopProtocol for H2/H3 regressions
  • Perf budget: TTFB p75 < 200ms, LCP p75 < 2.5s

What to say in interviews

“I’d run LCP breakdown in web-vitals to see whether TTFB, resource load delay, or element render delay dominates. If TTFB is high, fix edge caching. If render delay is high, inline critical CSS, SSR the hero, preload LCP image with fetchpriority high, and eliminate client-side hero fetch.”

Common traps

  • “dns-prefetch opens TCP” — only resolves name
  • “First byte = first paint” — parse/CSS/JS gap
  • “Shard across 10 subdomains” — extra DNS+TLS on H2/H3
  • “Prefetch all JS routes” — contends with LCP bandwidth

Last updated on