HTTP, performance & Core Web Vitals
Questions in this set 10
- 01Explain HTTP caching headers.
- 02HTTP/1.1 vs HTTP/2 vs HTTP/3.
- 03What are the Core Web Vitals and how do you fix each?
- 04Walk me through making a slow page fast.
- 05What is critical rendering path, and what blocks it?
- 06How do images and responsive loading work?
- 07What is CDN caching, and how do you invalidate it?
- 08Explain accessibility basics you would enforce in review.
- 09What causes a memory leak in a single-page app, and how do you find one?
- 10What is the difference between debouncing, throttling and requestAnimationFrame for scroll work?
Explain HTTP caching headers.
Two families. Freshness (Cache-Control: max-age=3600) lets the client use the copy with no request at all. Validation (ETag / Last-Modified) makes a conditional request (If-None-Match) that can return 304 Not Modified with no body.
The production pattern:
# hashed asset — immutable, cache for a year
Cache-Control: public, max-age=31536000, immutable
# HTML — never trust a stale shell, but let the CDN help
Cache-Control: no-cache # = revalidate every time (NOT "don't store")
Cache-Control: private, max-age=0, must-revalidate
# API you can serve slightly stale from a CDN
Cache-Control: public, s-maxage=60, stale-while-revalidate=600Distinctions that get asked: no-cache means "revalidate before using", no-store means "do not write it down at all" (use it for anything personal); s-maxage targets shared caches only; stale-while-revalidate serves stale instantly and refreshes in the background — the single best header for perceived speed on read-heavy APIs. Vary: Accept-Encoding, Cookie tells caches which request headers change the response — a missing Vary is how users get served each other's pages.
HTTP/1.1 vs HTTP/2 vs HTTP/3.
- 1.1: one request in flight per connection (pipelining never worked), so browsers open ~6 connections per origin. Hence the old tricks: sprites, domain sharding, concatenation.
- 2: one connection, multiplexed streams, header compression (HPACK), server push (now deprecated). Removes application-level head-of-line blocking — so bundling everything into one file is no longer automatically right.
- 3: same semantics over QUIC/UDP. Removes TCP-level head-of-line blocking (a lost packet no longer stalls unrelated streams), 0-RTT resumption, and connection migration across network changes — a real win on mobile.
What are the Core Web Vitals and how do you fix each?
| metric | measures | good | main fixes |
|---|---|---|---|
| LCP | largest element painted | < 2.5s | preload the hero image, fetchpriority="high", cut TTFB with CDN/caching, remove render-blocking CSS/JS |
| INP | responsiveness to interaction | < 200ms | break up long tasks, less JS, move work off the main thread, useTransition/scheduler.yield |
| CLS | unexpected layout shift | < 0.1 | width/height (or aspect-ratio) on media, reserve space for ads/banners, font-display: optional or preloaded fonts |
INP replaced FID in March 2024 — knowing that is a cheap freshness signal. Also know the difference between lab data (Lighthouse, synthetic, reproducible) and field data (CrUX/RUM, real users, what actually ranks). Optimise on field data; debug in the lab.
Walk me through making a slow page fast.
Method, not a list of tips:
- Measure in the field first (RUM p75, by device and country). Slow phones on 4G are the real users.
- Waterfall analysis — is it TTFB (server/CDN), resource loading (too much, too late), or execution (JS)?
- Reduce bytes: code-split by route, tree-shake, drop heavy dependencies (moment → date-fns/Temporal), compress with Brotli, serve AVIF/WebP, subset fonts.
- Fix the order: preconnect to critical origins, preload the LCP resource, defer non-critical JS and third-party scripts, inline critical CSS.
- Cache: immutable hashed assets, CDN in front of HTML,
stale-while-revalidateon APIs. - Do less on the main thread: virtualise long lists, debounce, move parsing to a worker, avoid layout thrashing.
- Re-measure and set a budget in CI (Lighthouse CI or bundle-size limits) so it does not regress.
What is critical rendering path, and what blocks it?
HTML is parsed into the DOM; CSS is parsed into the CSSOM and is render-blocking (the browser will not paint with an unstyled tree); synchronous scripts are parser-blocking and also wait for pending CSS, because a script may query computed style. So: put CSS in <head> and keep it small, mark non-critical CSS with a media query or load it asynchronously, and use defer/async/type=module for scripts. Fonts add a second stage — a webfont without font-display can hide text for up to 3 seconds (FOIT).
How do images and responsive loading work?
<img src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
width="1600" height="900"
fetchpriority="high" decoding="async" alt="…">srcset+sizes let the browser pick by device pixel ratio and layout width; width/height reserve space (fixes CLS); loading="lazy" for below-the-fold images but never for the LCP image; <picture> when you need art direction or format fallbacks. Modern formats: AVIF (smallest) with WebP and JPEG fallbacks.
What is CDN caching, and how do you invalidate it?
A CDN terminates TLS near the user and serves cached responses from the edge, cutting both latency and origin load. Cache key = URL + Vary headers (+ whatever you configure). Invalidation strategies, best first: content hashing in filenames (never invalidate, just change the URL); surrogate keys / cache tags for grouped purges; purge by URL as a blunt instrument; and short s-maxage with stale-while-revalidate so a purge is rarely urgent.
Explain accessibility basics you would enforce in review.
Semantic HTML first — a <button> gives you focus, keyboard activation and the right role for free; a <div onClick> gives you none of it. Then: every image has alt (empty alt="" for decorative), form inputs have associated <label>s, colour contrast ≥ 4.5:1 for body text, focus is visible and the tab order is logical, interactive elements are reachable and operable by keyboard, headings form a real outline, and dynamic updates are announced via aria-live. Use ARIA only when semantics do not exist — "no ARIA is better than bad ARIA". Test with keyboard-only navigation, axe DevTools, and a screen reader pass on the critical flow.
What causes a memory leak in a single-page app, and how do you find one?
Listeners and timers not cleaned up on unmount, subscriptions/websockets left open, closures capturing large trees, detached DOM held by a JS reference, and unbounded caches. Find them by taking heap snapshots in DevTools before and after repeating an action (navigate away and back ten times); a healthy app returns to roughly the same retained size. Sort by retained size, look for detached nodes, and follow the retainer chain to the code holding the reference.
What is the difference between debouncing, throttling and requestAnimationFrame for scroll work?
Debounce = act after activity stops (search input). Throttle = act at most once per interval (analytics on scroll). requestAnimationFrame = act once per frame, aligned to paint (any visual update driven by scroll). For visibility work, prefer IntersectionObserver, which does the job off the main thread with no scroll handler at all.