Top 42 Web Performance Interview Questions & Answers (2026)

42 Questions 20 Beginner 12 Intermediate 10 Advanced

About Web Performance

Top 50 web performance and Core Web Vitals interview questions covering LCP, CLS, INP, rendering, caching, image optimization, and performance tooling. Companies hiring for Web Performance roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.

What to Expect in a Web Performance Interview

Expect a mix of conceptual and practical Web Performance questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.

How to Use This Guide

Work through the Web Performance questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.

Curated by Tech Baithak Editorial Team  ·  Last updated: June 2026

Beginner 20 questions

Core concepts every Web Performance developer must know.

01

What are Core Web Vitals?

Core Web Vitals are a set of real-world, user-centric performance metrics defined by Google that measure a web page's user experience quality. Since 2021, they are a ranking signal in Google Search. The three current Core Web Vitals: LCP (Largest Contentful Paint): measures loading performance — when the largest image or text block is visible. Good: <2.5s. INP (Interaction to Next Paint): measures responsiveness — the slowest interaction (click, tap, key press) during the entire page visit. Replaced FID in March 2024. Good: <200ms. CLS (Cumulative Layout Shift): measures visual stability — how much page content unexpectedly shifts. Good: <0.1. Measure with: Google Search Console, PageSpeed Insights, Chrome DevTools, Chrome UX Report (real user data), Lighthouse (lab data). Core Web Vitals are part of a broader set of Web Vitals that also include TTFB (Time to First Byte) and FCP (First Contentful Paint).

Open this question on its own page
02

What is LCP (Largest Contentful Paint)?

LCP (Largest Contentful Paint) measures when the largest content element visible in the viewport (image, video poster, or block-level text) finishes rendering. It represents the perceived loading speed from the user's perspective. Good: <2.5 seconds. Needs improvement: 2.5–4 seconds. Poor: >4 seconds. Common LCP elements: hero images, H1 headings, above-the-fold video thumbnails. Common causes of poor LCP: slow server response (TTFB), render-blocking JavaScript and CSS, slow image loading (large unoptimized images), client-side rendering (JS renders the content, delaying LCP). Fixes: optimize server/TTFB, preload the LCP image (<link rel="preload" as="image">), convert images to WebP/AVIF, use a CDN, avoid lazy-loading the LCP image, eliminate render-blocking resources. LCP is the most impactful metric for perceived performance — users notice fast LCP as a "page loaded quickly."

Open this question on its own page
03

What is CLS (Cumulative Layout Shift)?

CLS (Cumulative Layout Shift) measures visual stability — the sum of all unexpected layout shift scores during a page's lifecycle. A layout shift occurs when a visible element changes position from one rendered frame to the next unexpectedly. Good: <0.1. Needs improvement: 0.1–0.25. Poor: >0.25. Layout shift score = impact fraction × distance fraction. Common causes: images and embeds without explicit width and height attributes (browser doesn't reserve space), ads and embeds injected above content, web fonts causing FOUT (Flash of Unstyled Text) that resizes text, content dynamically inserted above existing content. Fixes: always set width and height on images (or use CSS aspect-ratio), reserve space for ads with min-height, use font-display: optional to avoid layout shifts from font swapping, avoid inserting content above the fold after load. CLS is most noticed by users on mobile when they accidentally tap the wrong thing because the page shifted.

Open this question on its own page
04

What is INP (Interaction to Next Paint)?

INP (Interaction to Next Paint) replaced FID (First Input Delay) as a Core Web Vital in March 2024. It measures the responsiveness of a page to user interactions throughout the entire page lifecycle. INP captures the latency of all click, tap, and keyboard interactions and reports the worst-case (at 98th percentile). Good: <200ms. Needs improvement: 200–500ms. Poor: >500ms. Interaction latency breakdown: Input delay (time until the event handler starts) + Processing time (event handler execution) + Presentation delay (time to paint the next frame). Common causes of poor INP: long JavaScript tasks blocking the main thread, heavy event handlers, excessive DOM size, multiple forced layout calculations (layout thrashing). Fixes: break up long tasks with scheduler.yield() or setTimeout(fn, 0), defer non-critical JavaScript, use web workers for CPU-intensive work, avoid synchronous layout reads in event handlers.

Open this question on its own page
05

What is TTFB (Time to First Byte)?

TTFB (Time to First Byte) measures the time from when a user initiates a request to when the first byte of the response arrives at the browser. It reflects the combined time for: DNS lookup, TCP connection, TLS negotiation, and server processing time. Good: <800ms. Needs improvement: 800ms–1800ms. Poor: >1800ms. TTFB directly affects all other metrics — a slow server means LCP, FCP, and everything else is delayed. Causes of slow TTFB: slow server-side processing (database queries, API calls), absence of CDN, no response caching, slow hosting region far from users. Fixes: implement server-side caching (Redis, Varnish), deploy to CDN edge nodes, optimize database queries, use edge computing (Cloudflare Workers), use HTTP/2 or HTTP/3 for multiplexing, enable GZIP/Brotli compression, and implement connection reuse. For dynamic pages, even a 200ms TTFB makes a difference at scale.

Open this question on its own page
06

What is the critical rendering path?

The critical rendering path is the sequence of steps the browser takes to convert HTML, CSS, and JavaScript into pixels on the screen. Steps: 1. HTML Parsing: browser parses HTML and builds the DOM tree. 2. CSS Processing: parses CSS into the CSSOM tree. CSS is render-blocking — the browser waits for all CSS to download and parse before rendering. 3. JavaScript Execution: parser-blocking scripts pause HTML parsing. All scripts must execute before the browser can continue building the DOM. 4. Render Tree Construction: combines DOM + CSSOM, excluding invisible elements. 5. Layout: calculates each element's geometry (position, size). 6. Paint: fills pixels for each element. 7. Compositing: layers are combined and displayed. Optimizing the critical path: minimize render-blocking CSS (inline critical CSS), defer non-critical JavaScript, use async/defer on script tags, remove unused CSS, preload critical resources. The goal is to get content visible as fast as possible by shortening the critical path.

Open this question on its own page
07

What are render-blocking resources?

Render-blocking resources prevent the browser from painting any content until they are downloaded and processed. CSS files are render-blocking by default — the browser needs all CSS to build the CSSOM before rendering. JavaScript files without async or defer are parser-blocking — they pause HTML parsing and must execute before parsing continues. How to fix: Defer non-critical CSS: load with <link rel="preload" as="style"> and then set as stylesheet in onload. Or use CSS media queries: <link rel="stylesheet" href="print.css" media="print"> — print CSS doesn't block screen rendering. Use async/defer on scripts: <script defer src="app.js"> — defer executes after HTML parsing, maintains order. <script async src="analytics.js"> — async executes as soon as downloaded, any order. Inline critical CSS: put above-the-fold CSS directly in <style> tags. Module scripts: <script type="module"> are deferred automatically. Use Chrome DevTools Coverage tab to identify unused CSS/JS that can be removed.

Open this question on its own page
08

What is image optimization for web performance?

Image optimization is often the highest-impact performance improvement. Key techniques: Modern formats: use WebP (30% smaller than JPEG/PNG) or AVIF (50% smaller than JPEG) instead of JPEG/PNG. Use <picture> for format fallback: <source type="image/avif" srcset="img.avif"><source type="image/webp" srcset="img.webp"><img src="img.jpg">. Responsive images: use srcset and sizes to serve appropriately-sized images per device: srcset="img-400.jpg 400w, img-800.jpg 800w". Compression: use tools like Squoosh, imagemin, Sharp for lossy/lossless compression. Lazy loading: <img loading="lazy"> — defer loading off-screen images. Do NOT lazy-load the LCP image. Dimensions: always specify width and height to prevent CLS. CDN: serve images from a CDN close to users. Decoding: decoding="async" for off-screen images. Images typically account for 50–75% of page weight — optimizing them often provides the greatest performance gain.

Open this question on its own page
09

What are JavaScript performance best practices?

JavaScript is the primary source of interactivity issues and slow page loads. Key practices: Code splitting: split bundle by route/feature — users only download code for the current page. Use dynamic imports: const { default: Chart } = await import("./Chart"). Tree shaking: remove unused code — use ES modules and bundlers that support tree shaking (webpack, Rollup, Vite). Defer/async loading: non-critical scripts with defer or async. Avoid long tasks: any JS task >50ms blocks the main thread. Break up with setTimeout(fn, 0) or scheduler.yield(). Web Workers: CPU-intensive work (parsing, computation) off the main thread. Avoid unnecessary re-renders: in React, use memoization (React.memo, useMemo, useCallback). Debounce/throttle: limit frequency of expensive operations in event handlers. Third-party scripts: load analytics, ads, chat widgets after page load with async; use Partytown to run them in Web Workers. Bundle analysis: use webpack-bundle-analyzer or source-map-explorer to identify large dependencies.

Open this question on its own page
10

What is lazy loading and why is it important?

Lazy loading defers loading of non-critical resources until they are needed. Types: Native image lazy loading: <img loading="lazy"> — browser only loads images when they are near the viewport. Supported in all modern browsers. Easy win for pages with many images. JavaScript code splitting: split bundle into chunks loaded on demand. import("./HeavyComponent") loads the chunk only when needed. React lazy: const Chart = React.lazy(() => import("./Chart")) — combine with Suspense. Intersection Observer API: manually trigger loading when an element enters the viewport (for custom lazy-loading behavior). Lazy-load third-party widgets: load chat, social buttons after the page is interactive. Important exceptions: Never lazy-load the LCP image — it must load immediately. Never lazy-load above-the-fold content. Critical CSS should never be lazy-loaded. Lazy loading reduces initial page weight and allows browsers to prioritize critical resources, significantly improving LCP and TTI (Time to Interactive).

Open this question on its own page
11

What is browser caching and how does it improve performance?

Browser caching stores copies of resources (JS, CSS, images) locally so subsequent page visits are much faster. Controlled via HTTP response headers: Cache-Control: max-age=31536000, immutable for content-hashed assets (cache forever, the hash changes with content). no-cache: must validate with server before using (stale-while-revalidate pattern). no-store: never cache (sensitive data). ETag: fingerprint of the response — browser sends If-None-Match header; server returns 304 Not Modified if unchanged. Last-Modified: similar but time-based. Best strategy: use content hashing for JS/CSS bundles (e.g., app.a1b2c3.js) with very long max-age. Use short max-age + ETag for HTML files (need to pick up new asset filenames). Serve static assets from CDN with long cache TTLs. Service Worker cache: programmatic caching with full control, enables offline support. A well-cached site loads near-instantly on repeat visits — only the HTML (uncached or short TTL) needs to be fetched fresh.

Open this question on its own page
12

What is a CDN and how does it improve web performance?

A CDN (Content Delivery Network) is a distributed network of servers that delivers content from the node geographically closest to the user, reducing latency. Instead of all users hitting a single origin server (which might be in one city), CDN edge nodes in hundreds of locations serve cached content locally. Benefits: Reduced TTFB: serving from 50ms away vs 300ms away is a huge difference for LCP. Reduced server load: CDN absorbs most traffic, protecting the origin. Higher availability: geographic redundancy. Better performance globally: users in Asia get fast responses from Asian CDN nodes. What to put on a CDN: all static assets (JS, CSS, images, fonts). Optionally: SSR HTML responses with short TTLs and cache-vary by auth status. Edge computing: modern CDNs (Cloudflare Workers, Vercel Edge, Fastly Compute) can run server-side code at the edge, reducing TTFB for dynamic pages to near-CDN speeds. CDN selection: Cloudflare, Fastly, CloudFront, Akamai for static assets. For Next.js/Remix/SvelteKit apps, platforms like Vercel and Netlify are built-in CDNs.

Open this question on its own page
13

What is the difference between FCP and LCP?

FCP (First Contentful Paint) and LCP (Largest Contentful Paint) both measure loading performance but at different points. FCP: when any content (text, image, SVG, canvas) first appears on screen. It measures when the page starts to feel useful — the user sees something happening. Good: <1.8 seconds. FCP can be a small spinner or a navigation bar — not necessarily meaningful content. LCP: when the largest content element (image, video poster, or text block) finishes rendering. It measures when the main content is visible and the page feels loaded. Good: <2.5 seconds. LCP is always ≥ FCP since the largest element typically loads after smaller elements. Example: a page loads a logo (small, FCP fires), then the hero image (large, LCP fires). FCP and LCP together tell the story: FCP is the first sign of life, LCP is when the page feels complete. Large gaps between FCP and LCP indicate the main content is slow to load — often due to an unoptimized hero image or render-blocking resources before the main content.

Open this question on its own page
14

What is font optimization for web performance?

Web fonts are a common source of performance problems. Key optimizations: Preload critical fonts: <link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin> — loads the font early, before the CSS is parsed. Only preload the font used above the fold. font-display property: controls text rendering during font loading. font-display: swap: show fallback text immediately, swap when font loads (can cause CLS). font-display: optional: use font only if available within a short timeout, don't swap — best for LCP text and CLS prevention. font-display: block: invisible text for up to 3 seconds (FOIT — Flash of Invisible Text) — avoid. WOFF2 format: smallest file size, supported in all modern browsers. Subsetting: include only the characters needed. Variable fonts: one font file for multiple weights/styles. System font stack: skip web fonts entirely: font-family: system-ui, -apple-system, sans-serif — fast but less distinctive. Next.js, Astro, and Nuxt have built-in font optimization that automatically applies best practices.

Open this question on its own page
15

What is preloading, prefetching, and preconnecting?

Resource hints tell the browser to fetch resources in advance. preload: high-priority fetch for resources needed for the current page. Doesn't execute the resource — just downloads it. <link rel="preload" href="hero.jpg" as="image">. Use for: LCP image, critical fonts, above-the-fold CSS. Overuse causes bandwidth contention — only preload truly critical resources. prefetch: low-priority fetch for resources that might be needed for the next navigation. Fetched when the browser is idle. <link rel="prefetch" href="/dashboard.js">. Use for: JS bundles for the likely next page, heavy components. preconnect: establishes a TCP/TLS connection to a third-party origin in advance — DNS lookup + TCP + TLS handshake. <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>. Use for: CDN, API server, font provider. Saves 100-200ms per connection. dns-prefetch: only DNS lookup (lighter than preconnect). modulepreload: like preload but for ES modules — also parses and compiles. Order of priority: preload > prefetch. Don't overuse — too many preloads compete for bandwidth.

Open this question on its own page
16

What are web performance metrics and tools?

Key metrics and tools for measuring web performance: Metrics: LCP, INP, CLS (Core Web Vitals), FCP, TTFB, TTI (Time to Interactive), TBT (Total Blocking Time), Speed Index. Lab tools (synthetic, simulated): Lighthouse: built into Chrome DevTools; run in CI; audits performance, accessibility, SEO. WebPageTest: filmstrip, waterfall chart, advanced network simulation. Chrome DevTools Performance panel: flame chart, main thread analysis, layout shifts. Chrome DevTools Network panel: waterfall, timing, cache headers. Field data (real users): Chrome User Experience Report (CrUX): aggregated real-user data from Chrome users. Google Search Console: Core Web Vitals report from CrUX for your site. PageSpeed Insights: combines Lighthouse + CrUX data. Real User Monitoring (RUM): custom monitoring with web-vitals library: import { onLCP, onINP, onCLS } from "web-vitals"; onLCP(console.log). Lab data shows potential; field data shows real user experience. Always prioritize fixing field data (CrUX) since it is what Google uses for search ranking.

Open this question on its own page
17

What is code splitting and bundle optimization?

Code splitting divides JavaScript bundles into smaller chunks loaded on demand, reducing initial page load time. Types: Route-based splitting: each page/route gets its own JavaScript bundle. This is automatic in Next.js, Remix, and SvelteKit. Component-based splitting: dynamically import heavy components: const Modal = dynamic(() => import("./Modal")) in Next.js. Library splitting: separate vendor bundles for libraries that change less frequently — better long-term caching. Vite configuration: build.rollupOptions.output.manualChunks for custom splitting. Bundle analysis: webpack-bundle-analyzer: visual treemap of bundle contents. Bundlephobia: check library sizes before adding them. source-map-explorer: analyze bundle from source maps. Common optimization targets: moment.js (replace with date-fns or Day.js), lodash (use lodash-es + tree shaking), heavy chart libraries (lazy-load). Tree shaking: removes dead code. Requires ES modules (import/export, not require) and production builds. Effective tree shaking can reduce bundle by 30-50% for common libraries.

Open this question on its own page
18

What is service worker caching strategy?

A service worker is a JavaScript file that runs in the background, intercepting network requests and enabling programmatic caching. Key caching strategies: Cache First: check cache first, fall back to network. Best for: static assets, rarely-changing resources. Network First: try network, fall back to cache if offline. Best for: HTML, API data that must be fresh but also work offline. Stale While Revalidate: serve from cache immediately (fast), then update cache in background. Best for: non-critical content that can be slightly stale. Cache Only: only serve from cache, never go to network. Best for: pre-cached resources that are definitely available. Network Only: bypass cache entirely. Best for: real-time data, analytics. Workbox (by Google): library that implements these strategies declaratively: registerRoute(({ request }) => request.destination === "image", new CacheFirst({ cacheName: "images" })). Service workers also enable offline functionality, push notifications, and background sync. Next.js, Gatsby, and Astro have Workbox integration via plugins.

Open this question on its own page
19

What is HTTP/2 and HTTP/3 and how do they improve performance?

HTTP/2 was the first major HTTP revision since 1997, bringing several performance improvements: Multiplexing: multiple requests share one TCP connection simultaneously — eliminates HTTP/1.1's "head-of-line blocking" where each connection could only handle one request at a time. This makes domain sharding and concatenation anti-patterns unnecessary. Header compression (HPACK): reduces repeated header overhead between requests on the same connection. Server push: server can proactively send resources before the client requests them (largely deprecated in practice). Binary protocol: more efficient parsing than HTTP/1.1's text protocol. HTTP/3: uses QUIC (UDP-based) instead of TCP: Eliminates TCP head-of-line blocking: packet loss on one stream doesn't block others (unlike HTTP/2 over TCP). Faster connection establishment: 0-RTT or 1-RTT instead of TCP's 2-RTT. Connection migration: connections survive IP address changes (switching from WiFi to cellular). HTTP/3 provides the most benefit for high-latency or lossy connections (mobile networks, CDN edges). Both are supported by all major browsers and most CDNs/servers.

Open this question on its own page
20

What is GZIP vs Brotli compression?

Both are server-side text compression algorithms that reduce the size of HTML, CSS, JavaScript, JSON, and other text responses. GZIP: the traditional standard, supported everywhere. Typically reduces response size by 60-80%. Configure in Nginx: gzip on; gzip_types text/css application/javascript. Brotli: Google's modern compression algorithm. Achieves 15-20% better compression ratios than GZIP with similar compression speed. Supported in all modern browsers (IE doesn't support it). Use Brotli for modern browsers and GZIP as fallback. Browser signals support via Accept-Encoding: br, gzip header. Nginx Brotli: install ngx_brotli module. Static vs dynamic compression: statically pre-compress assets at build time for maximum compression levels. Dynamically compress responses for frequently-changing content. Impact: a 200KB JavaScript file compressed to 60KB with Brotli saves significant bandwidth, especially on mobile. Compression is one of the easiest wins — enable it on all text responses in production. Binary formats (images, video) are already compressed and should not be GZIP/Brotli compressed.

Open this question on its own page
Intermediate 12 questions

Practical knowledge for developers with hands-on experience.

01

How do you optimize React applications for performance?

React performance optimization techniques: Memoization: React.memo(Component) — skips re-render if props haven't changed. useMemo — memoize expensive computations. useCallback — memoize callback functions to prevent child re-renders. Code splitting: React.lazy + Suspense for route/component splitting. Virtualization: for long lists (1000+ items), use react-window or TanStack Virtual — only render visible rows. Avoid anonymous functions in JSX: onClick={() => handleClick(id)} creates a new function each render. Use useCallback or move out of render. Avoid inline objects in JSX props: style={{ color: "red" }} creates a new object each render. Extract to a constant. Concurrent features: React 18's useTransition marks slow updates as non-urgent, keeping the UI responsive. useDeferredValue defers updating a slow component. Server Components (Next.js App Router): components that render on the server — zero client JS bundle cost. Profile first: use React DevTools Profiler to identify actual bottlenecks before optimizing.

Open this question on its own page
02

What is the browser performance API and how do you measure performance?

The Performance API provides programmatic access to browser timing data. Key interfaces: performance.now(): high-resolution timestamp in milliseconds — more precise than Date.now(). Use for measuring code execution time: const start = performance.now(); doWork(); console.log(performance.now() - start). performance.mark() and performance.measure(): user timing API for marking specific points and measuring between them. Visible in DevTools. PerformanceObserver: observe performance entries as they occur: const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(entry.name, entry.startTime, entry.duration) } }); observer.observe({ entryTypes: ["largest-contentful-paint", "layout-shift", "longtask"] });. web-vitals library (Google): easiest way to collect Core Web Vitals: import { onLCP, onINP, onCLS, onFCP, onTTFB } from "web-vitals";. Long Task API: entryTypes: ["longtask"] — any main-thread task >50ms. Navigation Timing API: performance.getEntriesByType("navigation")[0] — complete page load timing breakdown.

Open this question on its own page
03

What is layout thrashing and how do you avoid it?

Layout thrashing (also called forced synchronous layout) occurs when JavaScript reads a layout-dependent property (e.g., element.offsetWidth) immediately after writing a style change, forcing the browser to recalculate layout synchronously before the main thread can proceed. This makes a single style change expensive and can cause severe performance issues when done in a loop. Example of thrashing: for (const el of elements) { el.style.width = box.offsetWidth + "px"; } — reads offsetWidth (forces layout recalculation) then writes, repeated N times. Fix: Batch reads, then writes: const width = box.offsetWidth; for (const el of elements) { el.style.width = width + "px"; }. Properties that force layout: offsetWidth/Height, scrollTop/Left, clientWidth/Height, getBoundingClientRect(), getComputedStyle(). Use requestAnimationFrame: group DOM writes in rAF callback — browsers batch paints before the next frame. CSS transform/opacity: use these for animations instead of properties that trigger layout. will-change: hint for elements that will animate — promotes to own compositor layer. FastDOM library schedules reads and writes correctly.

Open this question on its own page
04

What is resource prioritization in browsers?

Browsers assign priorities to resource fetches based on type and location. High priority: HTML, CSS (render-blocking), scripts at head, fonts, preloaded resources. Medium: images in viewport, regular scripts, AJAX. Low: images offscreen, scripts with defer. Fetch Priority API (priority hints): explicitly set priority: <img fetchpriority="high" src="lcp-hero.jpg"> for the LCP image. <script fetchpriority="low" src="analytics.js">. <link rel="preload" as="script" fetchpriority="low"> for less critical preloads. LCP image priority: the LCP image should be fetchpriority="high" — the browser's default priority for above-fold images may not be high enough. Importance of prioritization: browsers have limited concurrent connections even with HTTP/2 multiplexing. Correct prioritization ensures the resources that affect user perception (LCP element, interactive scripts) download first. Browser priority is often correct by default — only override when you have specific knowledge that the browser's heuristics are wrong for your page.

Open this question on its own page
05

How does SSR (Server-Side Rendering) improve performance?

SSR renders HTML on the server before sending it to the browser, improving several performance metrics. Benefits: Faster FCP and LCP: the browser receives complete HTML and can paint content without waiting for JavaScript to download and execute. A client-side rendered (CSR) app shows blank/loading screen until JS executes. Better SEO: search engine crawlers can index fully-rendered HTML without executing JavaScript. Lower time to meaningful content on slow devices: the server does rendering work; the client only parses HTML. Social sharing: Open Graph meta tags are present in the initial HTML. Trade-offs: TTFB increases: server must generate HTML per request — slower than serving a cached static file. Server cost: more CPU for rendering. Hydration cost: client still needs to "hydrate" the HTML with JavaScript, which can be slow (TBT impact). Streaming SSR: React 18 + Next.js/Remix support streaming — HTML chunks are sent progressively as they render, combining fast TTFB with full HTML. SSG (Static Site Generation): pre-render at build time for maximum performance with no SSR server cost.

Open this question on its own page
06

What is a performance budget?

A performance budget is a set of constraints on performance metrics that a team commits to maintaining. Types of budgets: Timing budgets: LCP < 2.5s, INP < 200ms, CLS < 0.1. Quantity budgets: total JS < 200KB (compressed), total CSS < 50KB, total page weight < 1MB. Rule-based budgets: no images > 100KB, Lighthouse score > 90. Why budgets matter: without explicit limits, performance degrades gradually — feature additions, third-party scripts, and dependencies accumulate. A budget creates accountability. Enforcing budgets: Lighthouse CI: block PRs that exceed budget. Configure in lighthouserc.json. Bundlesize: fail CI if bundle size grows beyond threshold. webpack performance hints: warn/error in build if bundle exceeds size. Monitoring: alert when real-user data (CrUX) degrades below threshold. Setting a budget: start with current performance as a baseline, set targets at the "good" threshold for Core Web Vitals. Add new features within the budget by removing technical debt or optimizing existing code.

Open this question on its own page
07

What is the impact of third-party scripts on performance?

Third-party scripts (analytics, ads, chat, social widgets, A/B testing) are one of the most common causes of poor Core Web Vitals in the real world. Their impact: LCP: scripts that execute before the LCP image is discovered delay loading. INP: third-party scripts add event listeners and run code on the main thread — long tasks delay interaction responses. CLS: ad scripts inject content dynamically, causing layout shifts. TBT: long-running third-party scripts block the main thread. Measurement: Chrome DevTools Network panel → filter by third-party domains. Mitigation strategies: Defer loading: use async or defer third-party scripts. Load after DOMContentLoaded. Partytown: run third-party scripts in a Web Worker, off the main thread. Facade pattern: show a placeholder (e.g., a static thumbnail for YouTube embeds) instead of loading the full embed until user interacts. Self-host: host analytics (Plausible, Umami) yourself to avoid third-party connections. Audit regularly: remove unused scripts. Google Tag Manager scripts especially accumulate hidden performance debt.

Open this question on its own page
08

What is the difference between First Input Delay (FID) and INP?

FID (First Input Delay) was replaced by INP (Interaction to Next Paint) in March 2024. Understanding the difference helps explain why INP is better. FID: measured only the first user interaction on a page — the delay from input event to the start of event handler processing. It excluded processing time and presentation time. Problematic: a page could have fast FID (first click was fast) but terrible responsiveness throughout the rest of the session. INP: measures all user interactions (clicks, taps, key presses) throughout the full page visit. Reports the worst-case interaction latency (98th percentile). Includes input delay + processing time + presentation delay. Much more representative of real user experience. INP is harder to achieve: FID could be gamed by ensuring the first interaction happened during an idle period. INP requires the entire page to be responsive at all times. Common INP issues that FID missed: React rendering triggered by clicks blocking the main thread, animation frame callbacks delaying paint, synchronous operations in click handlers.

Open this question on its own page
09

What is requestIdleCallback and how is it used for performance?

requestIdleCallback schedules a callback to run during the browser's idle time — when the main thread has no higher-priority work (rendering, event handling). Use it for non-critical, deferrable work that can be delayed without affecting user experience. Usage: requestIdleCallback((deadline) => { while (deadline.timeRemaining() > 0 && tasks.length > 0) { processTask(tasks.shift()); } if (tasks.length > 0) requestIdleCallback(processNextBatch); }). deadline.timeRemaining(): remaining idle time in milliseconds. deadline.didTimeout: whether the timeout expired. Options: { timeout: 2000 } — force execution after 2 seconds even if not idle. Use cases: analytics event batching, prefetching next page data, pre-computing derived state, sending logs, lazy initialization of non-critical modules. Limitations: not reliable for critical work (may not run for seconds on busy pages). Browser support is good but Safari was late to support it. The newer Scheduler API (scheduler.postTask()) provides more granular priority control. For React 18, useTransition and useDeferredValue use the scheduler internally.

Open this question on its own page
10

How do you optimize CSS for performance?

CSS optimization for performance: Critical CSS: extract above-the-fold CSS and inline it in <style> tags — eliminates render-blocking CSS for initial paint. Tools: Critical, Penthouse, Next.js does this automatically. Reduce CSS size: remove unused CSS with PurgeCSS or Tailwind CSS's JIT (only includes used utility classes). Avoid complex selectors: deeply nested selectors are slower to parse. BEM avoids deep nesting. Avoid layout-triggering properties in animations: animate only transform and opacity — they run on the GPU compositor without triggering layout or paint. Animating width, height, top, left triggers expensive layout. Contain: contain: layout or contain: strict limits the scope of layout recalculation to the contained element. will-change: will-change: transform promotes an element to its own compositor layer — use sparingly (memory cost). @layer: organize CSS with explicit cascade layers to avoid specificity battles and unnecessary overrides. CSS modules / scoped CSS: scoped styles prevent style leakage and enable unused CSS removal. Minimize specificity and nesting in production CSS.

Open this question on its own page
11

What is cumulative layout shift and how do you debug it?

Debugging CLS requires identifying what elements shift and why. Tools: Chrome DevTools Performance tab → "Layout Shifts" track shows individual shifts with the timestamp and score. The Rendering panel → "Layout Shift Regions" highlights shifting elements in green/blue. PageSpeed Insights shows which elements shifted in real user data. Common CLS causes and fixes: Images without dimensions: add width and height, or use CSS aspect-ratio: 16/9 on the container. Ads and embeds: reserve space with a container of fixed height. Web fonts: use font-display: optional or size-adjust CSS to make fallback font match web font metrics. Dynamically injected content: never inject content above existing content; use skeleton screens or fixed-height placeholders. Animations: transitions should use transform (no layout shift) not top/left/width/height. Cookie banners: position fixed at bottom (doesn't shift content) rather than sticky at top. The LayoutShiftAttribution API identifies which elements caused the shift: entry.sources.forEach(source => console.log(source.node)).

Open this question on its own page
12

What is server-sent events vs WebSockets for performance?

Server-Sent Events (SSE) and WebSockets serve different real-time communication patterns with different performance characteristics. SSE: unidirectional — server pushes to client only. Uses standard HTTP/1.1 or HTTP/2. Automatic reconnection built in. Works through proxies and firewalls. Browser sends Accept: text/event-stream; server streams data: ...\n\n text. Client: const es = new EventSource("/updates"). Performance: lightweight protocol, no extra handshake beyond HTTP. With HTTP/2, multiple SSE streams share one connection. WebSockets: bidirectional — both client and server can send. Requires protocol upgrade (ws:// or wss://). More complex proxy handling. Performance: lower overhead per message than HTTP requests, but the persistent connection consumes server resources. Choose SSE when: server pushes data (live feeds, notifications, progress), client doesn't need to send frequent messages, you want simple HTTP semantics. Choose WebSockets when: bidirectional communication (chat, multiplayer games, collaborative editing), low-latency required for client→server messages. SSE on HTTP/2 scales efficiently — each additional stream reuses existing multiplexed connections.

Open this question on its own page
Advanced 10 questions

Deep expertise questions for senior and lead roles.

01

What is the scheduler API and how does it improve INP?

The Scheduler API (scheduler.postTask()) provides fine-grained control over task execution priority on the main thread. Unlike setTimeout(fn, 0) (always low priority), the Scheduler API allows prioritizing tasks: user-blocking: highest priority — for work critical to user interactions. user-visible: medium — for visual updates the user will see. background: lowest — for non-urgent work. Usage: scheduler.postTask(() => heavyWork(), { priority: "background" }). scheduler.yield(): yield control back to the browser, then resume — crucial for INP: async function handleClick() { updateUI(); await scheduler.yield(); await processBigDataset(); }. By yielding after the initial UI update, the browser can paint the response to the click before the slow computation runs — improving INP. TaskController: cancel or reprioritize tasks dynamically. Impact on INP: breaking long tasks into yielded chunks prevents the 50ms+ blocking that causes poor INP. React 18's concurrent rendering uses a similar internal scheduler. scheduler.yield() is now the recommended way to chunk long tasks in event handlers.

Open this question on its own page
02

What is speculation rules API and prerendering?

The Speculation Rules API (Chrome 109+) enables full page prerendering — fetching, rendering, and executing a complete page in an invisible background tab before navigation. This enables near-instant page transitions. Define in a script tag: <script type="speculationrules">{"prerender": [{"where": {"href_matches": "/products/*"}, "eagerness": "moderate"}]}</script>. Eagerness levels: conservative: only when user starts hovering. moderate: when user hovers for 200ms. eager: as soon as the link is observed in the viewport. vs prefetch: prefetch only downloads resources. Prerendering fully renders the page — all JS executes, all API calls complete. Navigation feels instantaneous. Considerations: resource intensive — only prerender high-probability next pages. Respect privacy — don't prerender pages with analytics that fire on load. Use HTTP Cache-Control headers to prevent prerendered pages from being stale. Google Search uses it: search results pages are prerendered in mobile Chrome. When combined with good LCP, a prerendered page can achieve sub-100ms perceived load times from click to painted content.

Open this question on its own page
03

How do JavaScript virtual machines optimize JavaScript performance?

Understanding V8 (Chrome's JS engine) optimizations helps write faster JavaScript. JIT compilation: V8 interprets code initially (Ignition interpreter), then compiles frequently-run "hot" code to optimized machine code (TurboFan JIT compiler). Hidden classes (shapes): V8 creates a hidden class for each unique object shape. Creating objects with consistent property order reuses the same hidden class and enables faster property access. Inline caching: V8 caches the type of a value at a call site. Calling add(a, b) with always-number arguments is optimized. Calling with mixed types triggers deoptimization. Deoptimization triggers: adding properties to an object after creation, using delete on properties, changing property types, using arguments object. Array optimizations: typed arrays (Int32Array, Float64Array) have predictable element types — V8 stores them contiguously and uses unboxed representations. Mixing types in a regular array degrades to a slower "holey" representation. Practical implications: consistent object shapes, monomorphic functions (same types at call sites), typed arrays for numerical computation, avoiding dynamic property deletion.

Open this question on its own page
04

What is the Islands Architecture and partial hydration for performance?

Islands Architecture and partial hydration address the core performance problem of traditional SSR: even after sending full HTML, the entire JavaScript bundle must download and execute to "hydrate" the page before it becomes interactive. In a large app, this hydration can take seconds on mobile — high TBT and poor INP. Islands Architecture (Astro): only interactive "islands" hydrate with JavaScript. Static content sends zero JS. Each island hydrates independently with a specific strategy (load, idle, visible). Partial Hydration Frameworks: Qwik: the most radical approach — resumability instead of hydration. Serializes component state as HTML attributes. No hydration at all — JavaScript loads only when interaction occurs at the exact component that was interacted with. 0-RTT to interactive. Marko: automatic partial hydration — compiler determines which components need client-side JS. Fresh (Deno): islands architecture with Preact. SolidStart: fine-grained reactivity with partial hydration. The goal: ship the minimum JavaScript to make a page interactive, not the entire framework + components for the whole site. This is the frontier of web performance as of 2024.

Open this question on its own page
05

What are Container Queries and their performance implications?

CSS Container Queries allow components to adapt based on their container's size rather than the viewport, enabling truly reusable responsive components. Performance implications: Positive: fewer layout JavaScript hacks (ResizeObserver + class manipulation) replaced with native CSS. Native CSS is more performant than JS-driven responsive adjustments. Negative: containers require layout containment (container-type: inline-size), which creates new layout contexts. Each additional containment context adds a small overhead during layout recalculation. For deeply nested components with many containers, this can add up. Size queries: @container card (min-width: 400px) { .card-title { font-size: 2rem; } }. Style queries (experimental): @container style(--layout: grid) { ... } — query computed custom property values. Performance best practices: only apply container-type to elements that actually need size-aware children. Avoid applying it to every element. Use container-type: inline-size (cheaper) over size when you only need width queries. Container Queries replace many patterns that previously required JavaScript ResizeObserver and class toggling — the net performance impact is positive for most use cases.

Open this question on its own page
06

How do you implement performance observability in production?

Production performance observability requires collecting real user metrics (RUM) and acting on them. Instrument Core Web Vitals: import { onLCP, onINP, onCLS, onFCP, onTTFB } from "web-vitals"; function sendToAnalytics(metric) { navigator.sendBeacon("/analytics", JSON.stringify({ name: metric.name, value: metric.rating === "good" ? "good" : metric.rating === "needs-improvement" ? "ni" : "poor", delta: metric.delta, id: metric.id, page: location.pathname })); } onLCP(sendToAnalytics); onINP(sendToAnalytics); onCLS(sendToAnalytics);. Segment by dimensions: device type (mobile vs desktop), network speed (4G vs 3G), geography, page template. Commercial RUM tools: Datadog, Dynatrace, SpeedCurve, Calibre — provide p75/p95 distributions, time-series trends, regression alerts. Open source: send to BigQuery + Looker Studio, or ClickHouse + Grafana. Alerting: alert when 75th percentile LCP exceeds 2.5s or INP exceeds 200ms for more than 5% of sessions. Correlate with deployments: mark deployments in the time-series to correlate performance regressions with code changes. Real user data (field data) is the ground truth — lab data (Lighthouse) can miss user-specific issues like slow auth redirects or personalized content loading.

Open this question on its own page
07

What is HTTP/3 QUIC and how does it improve performance?

HTTP/3 uses the QUIC transport protocol (built on UDP) instead of TCP, addressing fundamental TCP limitations. TCP head-of-line blocking: HTTP/2 multiplexes multiple streams over one TCP connection. But TCP is ordered — a lost packet blocks ALL streams until it's retransmitted. On lossy networks (mobile, WiFi), this causes stalls. QUIC streams are independent — a lost packet only blocks the affected stream. Faster connection establishment: TCP requires 2 round trips (SYN, SYN-ACK, ACK + TLS 1.3 requires additional RTTs). QUIC combines transport + TLS in 1 or 0 RTTs for repeat connections. Sub-50ms connection times vs 150ms+ for TCP+TLS. Connection migration: QUIC connections survive IP address changes using connection IDs — switching from WiFi to cellular doesn't reset the connection. Improved congestion control: QUIC implements congestion control per-stream with more sophisticated algorithms than TCP's single-stream model. Adoption: supported by all major CDNs (Cloudflare, Fastly, CloudFront). Chrome, Firefox, Edge, and Safari support HTTP/3. Enable in Nginx with ngx_http_v3_module. Most beneficial for users on high-latency or lossy connections — mobile users in areas with poor connectivity see the most improvement.

Open this question on its own page
08

How do you optimize the interaction responsiveness (INP) of a complex SPA?

Optimizing INP in complex SPAs requires understanding the full interaction pipeline. Profile interactions: Chrome DevTools Performance tab → start recording, perform interaction, stop. The flame chart shows long tasks. Click the "Interaction" section in DevTools. Reduce input delay: avoid long tasks during the "hot periods" of user interaction. Defer non-critical work with scheduler.postTask(). Break up initialization code. Avoid synchronous operations in event listeners. Reduce processing time: minimize what happens in click handlers. Move expensive work out of the critical path: button.addEventListener("click", async () => { updateButtonAppearance(); // visual feedback first await scheduler.yield(); await expensiveWork(); }). Reduce presentation delay: minimize style invalidation scope with CSS containment. Avoid triggering layout after painting. React 18 patterns: wrap slow state updates in startTransition(() => setSlowState(newValue)) — React renders them asynchronously, not blocking the next frame. Long task budgeting: use PerformanceObserver to catch long tasks in CI: any task >50ms is a potential INP problem. Aim for event handlers to complete under 200ms total from input to presentation.

Open this question on its own page
09

What is the impact of memory leaks on web performance?

Memory leaks in web applications cause gradually degrading performance — pages become slow and unresponsive after extended use, requiring a reload. Common causes: Detached DOM nodes: removed elements still referenced by JavaScript (event listeners, closures). The garbage collector can't free them. Forgotten event listeners: adding event listeners without removing them, especially on global objects (window, document). Closures retaining large objects: a callback in a setInterval or setTimeout that captures a large object in its scope. Growing caches without eviction: in-memory caches (Maps, arrays) that grow indefinitely. React-specific: state updates on unmounted components, missing useEffect cleanup functions, subscriptions not cancelled on unmount. Detection tools: Chrome DevTools Memory tab → Heap Snapshot (compare snapshots to find growing objects). Performance Monitor (shows real-time JS heap). Allocation Timeline: find where allocations occur. Fixing leaks: use WeakMap/WeakSet for object associations (GC-friendly). Always clean up in useEffect return function: return () => subscription.unsubscribe(). Use AbortController for fetch requests. Clear timers and intervals. Detach event listeners on component unmount. Memory leaks are insidious — they don't cause immediate errors but degrade UX over time on long-lived sessions.

Open this question on its own page
10

What are the performance implications of React Server Components?

React Server Components (RSC), introduced in Next.js App Router and other frameworks, fundamentally change performance characteristics. Zero client JavaScript for server components: server components render to HTML on the server and their component code is never sent to the client bundle. A component fetching from a database + rendering a list contributes 0 bytes to the client bundle. Waterfalls eliminated: data fetching happens on the server in the same data center as the database — no network round trips between client and API. Component-level streaming: RSC enables streaming HTML chunks as server components resolve, using Suspense boundaries. Users see content progressively without waiting for all data. Client/Server boundary: the "use client" directive marks components that need interactivity (event handlers, hooks). Everything else stays server-side. Performance trade-offs: Higher server load: more rendering on the server. TTFB: first byte may be slower (server must start rendering before responding). Not a silver bullet: RSC is best for data-heavy components without interactivity. Over-using client components negates the benefits. Measurement: RSC improves TBT and TTI (less hydration JS) — measure both LCP and INP to confirm improvement.

Open this question on its own page
Back to All Topics 42 questions total