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.
| Phase | Typical cold cost | Key lever |
|---|---|---|
| DNS | 20–120ms | Cache, dns-prefetch |
| TCP + TLS | 2–3 RTT | preconnect, session resume |
| TTFB | Server + network | CDN, edge cache |
| Download | Bytes on wire | Compression, HTTP/2/3 |
| Parse + render | CSS + sync JS | Critical CSS, defer JS |
| LCP | Hero resource | Preload, SSR, fetchpriority |
DNS Lookup
Definition
The browser parses the URL, checks HSTS preload, then resolves hostname → IP through layered caches:
- Browser DNS cache
- OS cache
- Router/ISP resolver
- 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=highSharding 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:
- Edge cache HTML — cut TTFB
- Preconnect to API and font CDN
- Inline critical CSS, defer rest
- Preload LCP image with
fetchpriority="high" - Avoid sync third-party in
<head> - Code-split JS — route bundle not in critical path
- 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 → LCPPractical rules
- Measure each phase with Navigation Timing API
- preconnect > dns-prefetch for origins hit immediately
- Hero in SSR HTML, not client fetch
- Monitor
nextHopProtocolfor 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