Why Core Web Vitals Use the 75th Percentile — and What That Means for Your Fixes
This guide explains the assessment rule behind every threshold in Understanding Core Web Vitals Thresholds, part of Core Web Vitals & Measurement. A page passes LCP when the 75th percentile of its page loads is under 2.5s; INP when the p75 interaction latency is under 200ms; CLS when p75 layout shift is under 0.1. Not the average, not the median, not the worst case.
That one choice shapes everything about how you prioritise performance work. A p75 boundary means the fastest three quarters of your visits are irrelevant to pass/fail; only the slowest quarter matters, and within it, only the visits near the boundary. Teams that optimise the median — the "typical" user on a fast laptop — routinely ship improvements that leave the assessed number untouched.
Rapid Diagnosis: Where Does Your Distribution Sit?
- Pull the full distribution, not a single number. CrUX's BigQuery tables and the CrUX API expose histogram bins; your own RUM should store raw values. You need at least p50, p75 and p90.
- Compare p75 with p50. A small gap (p75 within 20% of p50) means a uniform experience — improvements must make everything faster. A large gap means a distinct slow population exists, and identifying it is the whole job.
- Check the share of "good" visits. CrUX reports good/needs-improvement/poor percentages. If 72% of visits are good, you need to move 3 percentage points of visits across the line — not speed up all of them.
- Segment the slow quarter. Break visits above p75 down by device class, connection type, country and page template. The cause is almost always concentrated.
Root Cause Analysis: Why p75 Behaves the Way It Does
1. It deliberately includes difficult conditions. The 75th percentile was chosen so that most users — three in four — get a good experience, while not being dominated by extreme outliers (the very slowest networks, bot-like traffic, broken devices) that a p95 or p99 would be. It is a balance between inclusiveness and stability.
2. It is stable enough to compare over time. Higher percentiles move erratically with small changes in traffic mix; a campaign that brings in users on slow networks for a weekend can swing p95 dramatically. p75 is far less sensitive to such shifts, which makes 28-day comparisons meaningful.
3. It hides the fast majority. Because pass/fail depends on a single quantile, improvements to visits already well below it have no effect. Shaving 300ms off visits that take 1.2s does nothing; shaving 300ms off visits that take 2.7s can flip the verdict.
4. It amplifies device and network mix. Your p75 is not a property of your code alone. Two sites with identical code but different audiences — one desktop-heavy, one mobile in emerging markets — will have very different p75s. That is why Core Web Vitals are assessed separately for mobile and desktop.
Step-by-Step: Moving the 75th Percentile Efficiently
1. Compute the distance to the boundary
The useful number is not "how slow is p75" but "what share of visits are above the threshold, and by how much". Compute both from raw RUM values.
-- Share of mobile page loads over the LCP threshold, and their median overshoot.
SELECT
COUNTIF(lcp > 2500) / COUNT(*) AS share_over,
APPROX_QUANTILES(IF(lcp > 2500, lcp - 2500, NULL), 2)[OFFSET(1)] AS median_overshoot_ms,
APPROX_QUANTILES(lcp, 100)[OFFSET(75)] AS p75
FROM rum.page_loads
WHERE device = 'mobile' AND ts > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 28 DAY);
-- trade-off: APPROX_QUANTILES is fast but approximate (~1% error). Near the
-- threshold that can matter; use exact percentile functions for the final check.
Expected outcome: a statement like "31% of mobile loads exceed 2.5s; the median overshoot is 600ms" — which tells you that cutting ~600ms from the slow population, not from everyone, is the goal.
2. Identify who the slow quarter is
SELECT device_memory, effective_type, template,
COUNT(*) AS loads, COUNTIF(lcp > 2500) / COUNT(*) AS share_over
FROM rum.page_loads WHERE device = 'mobile'
GROUP BY 1, 2, 3 HAVING loads > 500 ORDER BY share_over * loads DESC LIMIT 15;
-- trade-off: segment dimensions like device_memory are only available in
-- Chromium. For Safari-heavy audiences, segment by connection RTT and template.
Expected outcome: one or two segments — for example "4GB devices on 4G loading product pages" — account for most visits over the threshold. That segment is what you reproduce in the lab with matching throttling.
3. Fix for the segment, then measure the whole
Choose fixes that disproportionately help slow visits: fewer round trips (preconnect, Early Hints, inlined critical CSS) help high-latency connections far more than fast ones; less JavaScript helps low-memory devices more than flagships. Then re-measure the whole p75 — a fix that helps the slow segment by 800ms may move overall p75 by 300ms, which can be enough.
4. Watch the good-visit share, not just p75
The p75 is a coarse indicator; the share of good visits moves continuously and responds sooner. Track it daily from your RUM.
// Daily good-share for one metric, from raw RUM rows.
const goodShare = (rows, threshold) => rows.filter((r) => r.value <= threshold).length / rows.length;
console.log(`LCP good share: ${(goodShare(lcpRows, 2500) * 100).toFixed(1)}% (need >= 75%)`);
// trade-off: a good-share target of exactly 75% passes with no margin; CrUX
// uses a 28-day window and different sampling. Aim for 80%+ in your own RUM.
Expected outcome: a leading indicator that tells you within days whether a fix is working, instead of waiting for the 28-day CrUX window to roll over.
Verification
After shipping, verify in three places and in this order: your RUM good-share (days), your RUM p75 (a week), then CrUX p75 (up to 28 days, as old data ages out of the window). If RUM improves but CrUX does not, compare populations — CrUX vs your own RUM covers the usual reasons. Expect p75 changes in CrUX to appear gradually rather than as a step.
Common Misreadings of the p75 Rule
"Our average is 1.8s, so we pass." Averages are pulled down by fast visits and are not the assessed statistic. A page can average well under the threshold and fail at p75.
"We only need to fix 25% of users." Not quite: you need fewer than 25% of visits over the threshold. A single user with many slow visits counts many times. Heavy users on old devices can dominate the slow tail.
"Lab Lighthouse passes, so p75 will pass." Lighthouse emulates one device and one network profile, roughly a mid-tier phone on a slow 4G connection, and reports a single run. It says nothing about the distribution.
"Higher percentiles are irrelevant." They are irrelevant to the pass/fail verdict, but they are where your most frustrated users are. Track p90/p95 as health metrics even though they do not decide the assessment.
FAQ
Is the 75th percentile computed per page or per site?
Both, separately. CrUX computes p75 per URL when a URL has enough traffic, and per origin (all pages aggregated) always. Search Console groups similar URLs and reports group-level status. The distinction is covered in CrUX URL-level vs origin-level data.
For INP, is it the p75 of all interactions or of pages?
Of page visits. Each page visit contributes one INP value — its worst interaction, ignoring one outlier per 50 interactions — and the p75 is taken across visits. A visit with a hundred fast clicks and one slow one contributes that one slow value.
Related
- Passing desktop but failing mobile Core Web Vitals — why the two form factors have separate p75s.
- Computing p75 from raw RUM beacons — doing the percentile maths correctly in your own pipeline.
- Deriving performance budgets from Core Web Vitals — turning p75 targets into resource budgets.