Core Web Vitals — LCP, INP, CLS

The three metrics Google grades you on: loading, responsiveness, visual stability. Their thresholds, what causes each, the fixes, and why it's field data at the 75th percentile — not your laptop.

1The three vitals, at a glance

Each maps to one thing a user feels. Memorize the metric, the threshold, and the one-word feeling.

LCP

Largest Contentful Paint · loading
≤ 2.5s good · > 4s poor

Time until the largest element in the viewport (usually the hero image or H1 block) is painted. “Did the main content show up fast?”

INP

Interaction to Next Paint · responsiveness
≤ 200ms good · > 500ms poor

Worst-ish latency from a tap/click/keypress to the next frame, across the whole visit. “When I interact, does it respond?”

CLS

Cumulative Layout Shift · stability
≤ 0.1 good · > 0.25 poor

How much visible content jumps around unexpectedly (unitless score). “Did the page move under my thumb?”

Between good and poor is “needs improvement.” You pass a metric when ≥ 75% of real visits hit the “good” threshold. [web.dev]

One-liner

“Three vitals, three feelings: LCP = did it load, INP = does it react, CLS = does it hold still. Good = 2.5s / 200ms / 0.1, judged at the 75th percentile of real users.”

2The thing juniors miss: field data, p75

Core Web Vitals are scored from field data (real users, a.k.a. RUM / CrUX) — not your fast laptop on office wifi (lab data, e.g. Lighthouse). And it's the 75th percentile: you're graded by your slower quarter of visits, not the median.

One-liner

“Lab tells you why; field tells you whether. I debug in Lighthouse but I'm graded on p75 field data — so I instrument RUM and watch the slow quarter, not my own machine.”

3The supporting cast: TTFB → FCP → TTI

The three vitals don't stand alone — they sit on a timeline of diagnostic metrics. Google doesn't grade these, but they tell you where on the path a vital is breaking, so you can localize a regression instead of guessing.

TTFBfirst byte
FCPfirst pixel of content
LCPlargest element (vital)
TBT / TTImain thread frees up
INPreacts to input (vital)
Metric What it is Good Role
TTFB
Time to First Byte
request start → first byte of the HTML (server + network) ≤ 0.8s Precursor to everything — a slow TTFB caps your best-possible LCP.
FCP
First Contentful Paint
first any content painted (text, image) ≤ 1.8s First sign of life; precursor to LCP. Lab and field.
TBT
Total Blocking Time
sum of long-task (>50ms) blocking between FCP and interactive < 200ms (lab) The lab proxy for INP. This is what you optimize in Lighthouse when field INP is poor.
TTI
Time to Interactive
main thread quiet ~5s after FCP Deprecated — removed from Lighthouse 10 for being too variable. Say TBT instead.
The “you're current” detail: if you cite TTI as a target, you sound 2020. TTI was dropped from Lighthouse 10 because it was overly sensitive; the modern lab metric for responsiveness is TBT, and the field metric is INP. Name that and you signal you track the platform.
One-liner

TTFB → FCP → LCP is the loading timeline; TBT is the lab proxy for INP. TTI is legacy — Lighthouse dropped it for being too noisy, so I quote TBT now.”

Sources: web.dev — TTFB · web.dev — FCP · web.dev — TBT

4What causes each vital — and the fix

LCP — loading

Common cause Fix
Slow server / TTFB CDN, edge cache, SSR streaming, faster backend
Render-blocking CSS/JS (Lesson 02!) inline critical CSS, defer JS
LCP image discovered late / low priority preload + fetchpriority=high (Lesson 01!), AVIF/WebP, srcset
Lazy-loading the hero never lazy-load above-the-fold; lazy-load below it

INP — responsiveness (know this cold)

INP is the current interactivity metric — it replaced FID in March 2024. FID only measured the first interaction's input delay; INP measures the full latency of (nearly) every interaction all visit, so it's far harder to game. [web.dev]

An interaction's latency has three phases — naming them is the Lead-level answer:

Input delaymain thread busy → handler can't start
Processingyour event-handler JS runs
Presentationstyle/layout/paint the next frame
Cause Fix
Long tasks hog the main thread (input delay) break up long tasks, yield / scheduler.postTask, less JS
Heavy event handlers (processing) do the minimum sync work; defer non-urgent work, debounce
Big synchronous re-render (presentation) virtualize lists, content-visibility, move work off the click path
Hydration / third-party JS blocking code-split, partial/progressive hydration, audit tags

Optimizing INP in a React app

React 18's concurrent features map straight onto the three phases — the click path stays cheap, the expensive work goes non-urgent.

Input delay lazy+Suspense code-split, trim hydration (RSC / partial), less JS on the click path
Processing startTransition (mark the heavy state update non-urgent so the keystroke paints first), useDeferredValue (defer expensive derived UI), useMemo / Web Worker for heavy compute
Presentation virtualize lists (react-window), React.memo + stable keys, content-visibility:auto
// search filter that janks on every keystroke
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);        // input stays snappy
const results = useMemo(() => filterBig(deferredQuery), [deferredQuery]);
// onChange just does setQuery(...) — cheap + urgent
One-liner

“INP in React = keep the click path cheap. startTransition/useDeferredValue make heavy updates non-urgent, virtualize big lists, Web Worker the heavy compute. The urgent paint never waits on the expensive render.”

CLS — visual stability

Common cause Fix
Images/videos with no dimensions always set width/height or aspect-ratio
Ads / embeds / late content injected reserve space with a min-height placeholder
Web fonts swapping (FOUT) font-display + size-adjust to match metrics
Inserting content above existing content don't; or use transform (composite-only, no shift)
How CLS is actually calculated (be ready — it's a favourite deep-dive). Per frame:
layout shift score = impact fraction × distance fraction

Sources: web.dev — CLS · web.dev — INP · Google — Core Web Vitals · corewebvitals.io

5The Lead move: make it systemic

The toolkit is explicit that they grade systemic thinking, not “I lazy-loaded an image.” Tie the three vitals to the platform and to a guardrail:

Bundle-size budget vs Lighthouse budget — name both, they gate different things.
// budget.json — sizes (KB) / counts / timings (ms), by path
[{ "path": "/*",
   "resourceSizes": [{ "resourceType": "script", "budget": 300 },
                     { "resourceType": "total",  "budget": 600 }],
   "timings":       [{ "metric": "interactive",  "budget": 3000 }] }]

// OR lighthouserc.js assertions — gate audit scores/metrics directly
assert: { assertions: {
  'categories:performance':     ['error', { minScore: 0.9 }],
  'largest-contentful-paint':   ['error', { maxNumericValue: 2500 }],
  'cumulative-layout-shift':    ['error', { maxNumericValue: 0.1 }] }}
Runs via @lhci/cli in CI and fails the build when exceeded. Gotchas: it's budget.json or assertions, not both; budget.json can only assert sizes/counts, not metric scores like LCP.
One-liner

“Bundle budget gates the artifact; Lighthouse budget gates the rendered page — real LCP/CLS and total weight including images & third-party. I run both in CI so neither blind spot ships.”

Full loop

Concept: LCP/INP/CLS = load/react/stability, graded on p75 field data. Trade-off: chasing a lab Lighthouse score can diverge from real users — over-optimizing the median while the slow quarter (real devices, INP) stays red. Anchor: “Our Lighthouse was green but field INP was poor; the culprit was a third-party tag's long tasks — we deferred it and split our bundle.” Impact: CWV is a Google ranking signal and correlates with conversion on a booking funnel. Invite: “I'd weight INP higher on an interactive app, LCP higher on a content/SEO page.”

6Check yourself — scenario quiz

Pick an answer; instant feedback. Push-back style, like the round.

1. Match the “good” thresholds.

2. Your Lighthouse score is 98 (green) but the field report flags INP as poor. Most likely explanation?

They're testing lab-vs-field understanding.

3. INP is poor. The flame chart shows interactions wait ~250ms before the handler even starts. Which phase, and a fix?

4. Hotel cards jump down as each photo loads. Which vital, and the fix?

5. Why did Google replace FID with INP in 2024?

6. Best Lead answer to “how do you keep Core Web Vitals good across many teams?”

7. Field INP is poor, but you can only test in Lighthouse right now. Which lab metric do you optimize, and what about TTI?

“You're current” probe — they want to hear TTI is legacy.

8. LCP is 5s. The waterfall shows TTFB alone is 2.6s. First thing you address?

9. An element covering 50% of the viewport shifts down by 20% of viewport height. The layout shift score for that frame?

10. A React search box janks on every keystroke because it filters a 5,000-item list synchronously. Best INP fix?

0 / 10 answered

Try this aloud before next session: “Name the three Core Web Vitals, their thresholds, the one feeling each maps to, and for our hotel listing page give one cause and one fix for each.” Time to 90 seconds.
Good follow-up topics:
“Quiz me out loud, harder” “How is CLS actually calculated?” “TTFB / FCP / TTI — where do they fit?” “Optimize INP in a React app?” “What's in a perf budget?”