On this page · sections
Core Web Vitals: LCP, INP, CLS
Core Web Vitals are three user-centric metrics Google uses for Search ranking signals — measured at p75 field data, not lab median.
| Metric | Measures | Good (p75) | Poor (p75) |
|---|---|---|---|
| LCP | Largest content paint time | ≤ 2.5s | > 4.0s |
| INP | Worst interaction latency | ≤ 200ms | > 500ms |
| CLS | Unexpected layout shift sum | ≤ 0.1 | > 0.25 |
LCP, INP, and CLS Definitions
Definition
LCP (Largest Contentful Paint) — when the largest above-fold content paints. Candidates: img, video poster, svg image, block with background-image, text blocks (p, h1, …). Excludes below-fold, opacity 0, or elements removed before load.
INP (Interaction to Next Paint) — replaced FID in March 2024. Worst interaction latency across page lifetime: input delay + processing + presentation delay.
CLS (Cumulative Layout Shift) — sum of unexpected layout shift scores. Score = impact fraction × distance fraction. Modern spec uses session windows with caps.
const THRESHOLDS = {
LCP: { good: 2500, poor: 4000 },
INP: { good: 200, poor: 500 },
CLS: { good: 0.1, poor: 0.25 },
};
function isLcpCandidate(el) {
const tag = el.tagName;
if (tag === 'IMG' || tag === 'VIDEO') return true;
const style = getComputedStyle(el);
if (style.backgroundImage && style.backgroundImage !== 'none') return true;
return ['P', 'H1', 'H2', 'H3', 'DIV'].includes(tag);
}All three use 75th percentile field data. CrUX = public Chrome field data (28-day rolling). LCP ≠ TTFB — often after resource load + render.
Interview: p75 because median hides slow tail on low-end devices. INP replaced FID.
Lab vs Field Measurement
Definition
Field (RUM): web-vitals v4+ — onLCP, onINP, onCLS. Send id, delta, rating, navigationType for segmentation.
Lab: Lighthouse, DevTools Performance panel, Performance Insights (LCP breakdown: TTFB, load delay, render delay).
function sendToAnalytics({ name, value, id, rating, navigationType }) {
navigator.sendBeacon('/vitals', JSON.stringify({
name, value, id, rating, navigationType,
path: location.pathname,
}));
}
import('https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js')
.then(({ onLCP, onINP, onCLS }) => {
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
});
// Lab read — use last LCP entry
window.addEventListener('load', () => {
const entries = performance.getEntriesByType('largest-contentful-paint');
const last = entries[entries.length - 1];
console.log('Lab LCP ms:', last?.renderTime || last?.loadTime);
});LoAF (Long Animation Frames) complements INP for attribution — which callback caused delay. Never ship based on local Lighthouse alone.
Interview: Lab reproduces fixes; field validates impact. CrUX lags 28 days — your RUM is faster feedback.
Fixing Poor LCP
Definition
LCP breakdown in Performance Insights:
| Subpart high | Fix |
|---|---|
| TTFB | CDN, edge cache, SSR cold start |
| Resource load delay | Preload, fetchpriority="high", SSR hero in HTML |
| Resource load duration | WebP/AVIF, compression, CDN |
| Element render delay | Critical CSS inline, defer JS, reduce hydration |
<!-- Fix — discoverable in first HTML chunk -->
<img src="/hero-800.webp"
srcset="/hero-800.webp 800w, /hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 1200px"
width="1200" height="630"
fetchpriority="high"
alt="Product hero">Anti-pattern: fetch hero config in JS after hydration — LCP waits on JS + network + render.
Interview: Put LCP element in server HTML. Preload hero image. Don’t client-fetch the hero.
Fixing Poor INP
Definition
INP = slowest interaction. Common causes:
- Long tasks on main thread (>50ms) during input handling
- Large React re-renders on click
- Heavy third-party scripts
- Synchronous layout reads in event handlers
Fixes:
- Break up long tasks —
scheduler.yield(), chunking - Defer non-urgent updates —
startTransition, debounce - Optimize event handlers — avoid layout thrashing
- Code-split heavy routes
- Offload compute to workers
button.addEventListener('click', () => {
// Urgent — immediate feedback
button.setAttribute('aria-busy', 'true');
// Non-urgent — defer
startTransition(() => {
applyHeavyFilter(largeDataset);
});
});Debug locally: Performance panel → Interactions → slow tap → follow long task stack. Throttle 4× CPU.
Interview: INP is worst-case interaction, not average. Find the slowest tap/click/keypress.
Fixing Poor CLS
Definition
CLS from elements shifting without user input. Common causes:
- Images/iframes without width/height
- Web fonts causing FOIT/FOUT reflow
- Dynamic content injected above existing content
- Ads/embeds loading late
Fixes:
/* Reserve space */
.hero img {
width: 100%;
aspect-ratio: 1200 / 630;
}
/* Font fallback with similar metrics */
@font-face {
font-family: 'Inter';
font-display: swap;
size-adjust: 100%;
}<!-- Skeleton with fixed height -->
<div class="ad-slot" style="min-height: 250px"></div>Use transform animations instead of layout-affecting properties. For SPA route changes, maintain scroll position and skeleton dimensions.
Interview: Always set explicit dimensions on media. Use
font-display: swap+ size-adjust. Never inject content above LCP without reserved space.
Senior Interview Summary
Mental model
LCP → Loading performance (largest paint)
INP → Interactivity (worst interaction latency)
CLS → Visual stability (unexpected shifts)
All measured at p75 field dataPractical rules
- RUM with web-vitals on 10–30% sample
- LCP breakdown → fix largest subpart first
- INP → profile slowest interaction, break long tasks
- CLS → dimensions on images/ads, font metrics matched
- Perf budget: LCP < 2.5s, INP < 200ms, CLS < 0.1 at p75
What to say in interviews
“I’d instrument web-vitals in production and segment by device and route. For LCP I’d check the breakdown — if TTFB is high, fix caching; if render delay is high, SSR the hero and inline critical CSS. For INP I’d find the worst interaction in Performance panel and break up long tasks. For CLS I’d reserve space for images and ads and match fallback font metrics.”
Common traps
- Optimizing Lighthouse score without field validation
- Confusing LCP with TTFB
- FID is deprecated — INP is the responsiveness metric
- Logging every CLS delta in prod instead of session aggregate