Skip to content

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.

Rajesh DhimanMarch 01, 2026~18 min
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:

  1. 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).
  2. 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.
  3. 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"]
  1. 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).
  2. 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> with fetchpriority="high" and <link rel="preload">.
  3. 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 srcset and sizes, correct CDN compression, avoiding 4MB PNGs.
  4. 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
  1. Input Delay: Waiting for background tasks, timer callbacks, or hydration work to vacate the main thread.
  2. Processing Time: The CPU time spent executing registered event handlers (onClick, onKeyDown).
  3. 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:

  1. 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>, add fetchpriority="high", convert to AVIF.
  2. 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 useTransition or scheduler.yield(), memoize cart summary rows with React.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-ratio or explicit width/height.
  • Google ranking uses 75th percentile (p75) field data from CrUX, not Lighthouse lab scores.

Written by Rajesh Dhiman at Eunix Tech.

Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback
JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

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.