Core Web Vitals: The Mental Model (LCP, INP, CLS)
A high-signal mental model for LCP, INP, and CLS: what they measure, what actually moves the needle, and how to debug them in real apps.
On this page
30‑Second Senior Interview Answer
Core Web Vitals (CWV) are a standardized set of user-centric performance metrics defined by Google that directly impact search engine ranking and user conversion:
- LCP (Largest Contentful Paint): Measures perceived loading speed. It records the render timestamp of the largest visible image, video poster, or text block in the viewport. Target: ≤ 2.5s at the 75th percentile (p75).
- INP (Interaction to Next Paint): Measures runtime responsiveness. Officially replacing First Input Delay (FID) in March 2024, INP evaluates the end-to-end latency of all user interactions (clicks, taps, keystrokes) throughout the entire page lifecycle. Target: ≤ 200ms at p75.
- CLS (Cumulative Layout Shift): Measures visual stability. It aggregates unexpected layout shift scores caused by late-loading assets, dynamic ads, or un-dimensioned elements. Target: ≤ 0.1 at p75.
Optimizing CWV requires distinguishing Lab data (synthetic simulations like Lighthouse) from Field data (Real User Monitoring / CrUX over 28-day rolling windows).
1. Metric Overview & Thresholds
flowchart LR
subgraph Loading["1. Loading"]
LCP["LCP (Largest Contentful Paint)<br/>Target: ≤ 2.5s"]
end
subgraph Responsiveness["2. Responsiveness"]
INP["INP (Interaction to Next Paint)<br/>Target: ≤ 200ms"]
end
subgraph Stability["3. Visual Stability"]
CLS["CLS (Cumulative Layout Shift)<br/>Target: ≤ 0.1"]
end| Metric | Good (Pass) | Needs Improvement | Poor | Primary Culprit |
|---|---|---|---|---|
| LCP | ≤ 2.5s |
2.5s – 4.0s |
> 4.0s |
Slow TTFB, render-blocking CSS/JS, oversized hero media |
| INP | ≤ 200ms |
200ms – 500ms |
> 500ms |
Main-thread long tasks (> 50ms), heavy hydration, layout thrashing |
| CLS | ≤ 0.1 |
0.1 – 0.25 |
> 0.25 |
Images/embeds without dimensions, FOIT/FOUT font swaps, injected DOM |
2. LCP Deep Dive: The 4 Sub-Parts Breakdown
In senior interviews, describing LCP simply as "image load time" is insufficient. Chrome's performance team breaks down LCP into 4 distinct sub-parts:
flowchart LR
TTFB["1. TTFB<br/>(Time to First Byte)"] --> Delay["2. Resource Load Delay"]
Delay --> Duration["3. Resource Load Duration"]
Duration --> Render["4. Element Render Delay"]- TTFB (Time to First Byte, ~40% of budget = ~1000ms):
- The time from navigation start until the browser receives the first byte of HTML.
- Optimization: Edge CDN caching, optimized database queries, HTTP/2 or HTTP/3 server push/early hints (
103 Early Hints).
- Resource Load Delay (~10% of budget = ~250ms):
- The delta between the first byte of HTML and when the browser begins requesting the LCP resource.
- The Trap: Hiding the hero image inside an external CSS file (
background-image: url(...)) or injecting it via client-side JavaScript. - Optimization: Include the hero image directly in the initial HTML using
<img>withfetchpriority="high"and<link rel="preload">.
- Resource Load Duration (~40% of budget = ~1000ms):
- The time it takes to download the asset bytes over the network.
- Optimization: Modern image formats (AVIF/WebP), responsive
srcsetandsizes, correct CDN compression, avoiding 4MB PNGs.
- Element Render Delay (~10% of budget = ~250ms):
- The time between when the image bytes finish downloading and when the browser paints it to the screen.
- Optimization: Remove synchronous render-blocking stylesheets or long JavaScript execution tasks holding the main thread before the paint phase.
High-Impact LCP Code Pattern
<head>
<!-- Preconnect to CDN -->
<link rel="preconnect" href="https://cdn.frontendinterview.in" crossorigin />
<!-- Preload LCP Candidate with High Fetch Priority -->
<link
rel="preload"
as="image"
href="/hero-desktop.avif"
type="image/avif"
fetchpriority="high"
/>
</head>
<body>
<!-- Clean semantic <img> with explicit dimensions and no lazy-loading on LCP -->
<img
src="/hero-desktop.avif"
width="1200"
height="630"
alt="Frontend Architecture"
fetchpriority="high"
decoding="async"
/>
</body>[!WARNING] The LCP Lazy-Loading Anti-Pattern: Never add
loading="lazy"to the LCP element. Browsers delay lazy-loaded images until layout is calculated, severely worsening Resource Load Delay.
3. INP Deep Dive: The March 2024 Transition from FID
One of the most critical updates in web performance is the deprecation of FID (First Input Delay) in favor of INP (Interaction to Next Paint).
Why was FID Replaced?
- FID was misleadingly optimistic: It only measured the input delay (the time between user interaction and when event handlers started executing). It did not measure the time spent executing event handlers or the time required to paint the resulting visual update!
- FID only tracked the first interaction: A page could pass FID on load, but freeze completely when a user clicked a dropdown or modal 30 seconds later.
- INP measures the full journey across ALL interactions: Clicks, taps, and keyboard inputs are tracked across the session. INP reports the worst (or near-worst) interaction latency.
The 3 Phases of an Interaction
sequenceDiagram
participant User
participant Browser as Main Thread
participant Compositor as GPU / Compositor
User->>Browser: Interaction (Click / Keypress)
Note over Browser: 1. Input Delay (Waiting for running tasks)
Note over Browser: 2. Processing Time (Running JS Event Listeners)
Note over Browser: 3. Presentation Delay (Style, Layout, Paint)
Browser->>Compositor: Frame Submitted
Compositor->>User: Next Frame Painted to Screen- Input Delay: Waiting for background tasks, timer callbacks, or hydration work to vacate the main thread.
- Processing Time: The CPU time spent executing registered event handlers (
onClick,onKeyDown). - Presentation Delay: The time required for the browser to recalculate styles, execute reflow/layout, rasterize pixels, and composite the resulting frame.
Production INP Optimizations
1. Yielding to the Main Thread via scheduler.yield()
Long JavaScript tasks (> 50ms) block the main thread. Break them into micro-chunks:
async function handleFilterChange(items) {
// 1. Immediate visual feedback (input delay ends)
setLoadingSpinner(true);
// 2. Yield control back to browser to allow frame paint:
if ('scheduler' in window && 'yield' in window.scheduler) {
await window.scheduler.yield();
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
// 3. Heavy computation proceeds without freezing UI
const filtered = runExpensiveFiltering(items);
renderResults(filtered);
}2. Avoid Heavy State Cascades During Keystrokes
When a user types into an input field, do not trigger heavy sub-tree re-renders synchronously. Keep controlled input state local, and debounce or defer filtered lists with React 18's useDeferredValue or useTransition.
4. CLS Deep Dive: Layout Stability Mathematics
Cumulative Layout Shift measures the total sum of all individual layout shift scores for every unexpected layout shift that occurs throughout the page life.
The Mathematical Formula
$$\text{Layout Shift Score} = \text{Impact Fraction} \times \text{Distance Fraction}$$
- Impact Fraction: Measures how unstable elements impact the viewport area between two frames. If an element occupying 50% of the viewport shifts, the impact fraction is
0.50. - Distance Fraction: Measures the greatest distance an unstable element has moved relative to the viewport height or width. If it moved by 20% of viewport height, the distance fraction is
0.20. - Total Shift Score: $0.50 \times 0.20 = 0.10$.
Common Culprits & Production Solutions
/* 1. Prevent image/video layout shifts using aspect-ratio */
.responsive-banner {
width: 100%;
aspect-ratio: 16 / 9;
background-color: #1e293b; /* Skeleton color */
}
/* 2. Prevent font layout shifts (FOIT/FOUT) */
@font-face {
font-family: 'CustomSans';
src: url('/fonts/custom.woff2') format('woff2');
font-display: optional; /* Zero CLS: only uses custom font if cached */
}5. Lab Data (Lighthouse) vs Field Data (CrUX / RUM)
An essential distinction expected of senior developers:
| Feature | Lab Data (Lighthouse, WebPageTest) | Field Data (CrUX, RUM) |
|---|---|---|
| Environment | Controlled, synthetic device & network profiles | Real users on thousands of disparate devices |
| INP Measuring | ❌ Cannot measure (no real user interactions) | ✅ Real interactions across diverse hardware |
| Caching | Usually runs on cold cache | Mix of cold, warm, and edge-cached visits |
| SEO Impact | Informational debugging aid | Direct ranking signal (Google evaluates p75 Field CrUX) |
Monitoring CWV in Real-Time (RUM)
Using the official Google web-vitals library:
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
delta: metric.delta,
id: metric.id,
});
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/analytics/vitals', body);
}
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);6. Interview Case Study: Diagnosing a Sluggish Checkout
Scenario: An e-commerce checkout page experiences an INP of 650ms and an LCP of 4.2s. How would you systematically diagnose and fix it?
Diagnostic Process:
- LCP Investigation:
- Open Chrome DevTools → Performance Panel → Record page reload with 4x CPU slowdown and Fast 3G.
- Locate the LCP diamond in the timeline. Identify the candidate element.
- Inspect the network waterfall: Is the image discovery delayed by CSS imports? Is TTFB high?
- Fix: Move hero image to HTML
<img>, addfetchpriority="high", convert to AVIF.
- INP Investigation:
- In Performance Panel, interact with the checkout form (type coupon code, click shipping dropdown).
- Look for red-striped Long Tasks (> 50ms) under the Main thread track.
- Inspect bottom-up flame charts: Is React re-rendering the entire 500-item cart on every keystroke?
- Fix: Wrap coupon input in
useTransitionorscheduler.yield(), memoize cart summary rows withReact.memo.
Summary Checklist
- LCP target is ≤ 2.5s; optimize the 4 sub-parts (TTFB, discovery delay, load duration, render delay).
- Never lazy-load the LCP element; use
fetchpriority="high"and preload. - INP replaced FID in March 2024; it evaluates total latency (Input Delay + Processing + Presentation).
- Use
scheduler.yield()or microtask slicing to break up long tasks (> 50ms). - CLS target is ≤ 0.1; always reserve space with
aspect-ratioor explicit width/height. - Google ranking uses 75th percentile (p75) field data from CrUX, not Lighthouse lab scores.
Written by Rajesh Dhiman at Eunix Tech.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.