logicentity.com
Performance

The Calculus Behind Web Vitals

LCP is a supremum. CLS is an integral. INP is a quantile. Here is the math beneath the metrics.

Pradeep DavuluriFebruary 26, 202612 min read
Performance Engineering

The Calculus Behind Web Vitals

How derivatives, integrals, and rate-of-change thinking underpin every Core

01

Why Calculus? Seriously.

Every time Chrome records a Core Web Vital, it's doing something that looks suspiciously like a calculus operation. It's measuring rates of change, accumulating displacement over time, finding maximum values on continuous curves, and computing percentiles across distributions. We just don't call it calculus because the marketing copy says "user experience metrics."

But strip back the abstraction and you'll find that LCP is a supremum problem, CLS is a definite integral over a displacement function, INP is a percentile operation on a probability distribution, and TTFB is a first-derivative boundary condition. Understanding the math doesn't just satisfy curiosity — it changes how you optimize. You stop chasing numbers and start reasoning about the shape of the underlying functions.

This post assumes you're comfortable with Web Vitals at a practical level and vaguely remember what a derivative is. If you remember that a derivative measures how fast something is changing and an integral measures how much of something has accumulated — you're good. We'll rebuild the rest from there.

Notation We'll use t for time (milliseconds from navigation start), f(t) for generic continuous functions of time, and subscript notation like tLCP for the timestamp at which a specific metric finalizes. Nothing here requires formal proofs — the goal is intuition, not rigor.
02

LCP — The Area Under the Render Curve

Largest Contentful Paint is deceptively simple on the surface: find the largest image or text block that renders in the viewport, record the timestamp. But "largest" is doing a lot of work. The browser is continuously evaluating a candidate function — a monotonically non-decreasing step function of rendered element areas over time.

LCP = sup { A(e) : eE(t) }
Where A(e) is the area of element e, and E(t) is the set of contentful elements rendered by time t

The supremum — the least upper bound — is the calculus operation hiding inside LCP. The browser doesn't just find the largest element; it tracks the running maximum of a set that grows over time. Every time a new element paints, the candidate set E(t) expands, and the browser re-evaluates whether the supremum has changed.

Why This Matters for Optimization

If you model the render timeline as a step function where each step is a new contentful paint, LCP is the x-coordinate of the final step in the staircase of the maximum-area candidate. This gives us a crucial insight: LCP is sensitive to paint order, not just paint speed. If your hero image renders at 2.4s but a larger carousel image renders at 3.8s, LCP jumps to 3.8s — even though you "optimized" the hero.

// LCP as a running supremum
const observer = new PerformanceObserver((list) => {
  let maxArea = 0;
  let lcpTime = 0;

  for (const entry of list.getEntries()) {
    // sup{A(e)} — update if new candidate is larger
    if (entry.size > maxArea) {
      maxArea = entry.size;
      lcpTime = entry.startTime;
    }
  }
});

observer.observe({ type: 'largest-contentful-paint', buffered: true });

The key optimization heuristic follows directly from the math: minimize tLCP by ensuring the supremum of A(e) is reached as early as possible. In practice this means: preload the largest contentful element, ensure it doesn't get displaced by a later, larger element, and eliminate render-blocking resources that delay the paint of your intended LCP candidate.

Practical takeaway Run performance.getEntriesByType('largest-contentful-paint') in your console and check the size property of each entry. If the final entry isn't your hero element, you have a candidate ordering problem — not a speed problem.
03

CLS — Displacement as a Definite Integral

Cumulative Layout Shift is the most overtly mathematical Web Vital. The name itself contains the word "cumulative" — and accumulation is what integrals do. CLS sums up every unexpected layout shift that occurs during the page's lifetime, where each shift has a score computed from two factors: the impact fraction (how much viewport area was affected) and the distance fraction (how far elements moved).

CLS = maxw  Σiw  IFi × DFi
Maximum session window (w) sum of impact fraction × distance fraction for each shift i

If we think of the layout as a vector field — every element has a position, and shifts are displacement vectors — then CLS is the discrete integral of the magnitude of displacement, weighted by the area each displaced element occupies. The "session window" constraint (5s maximum window, 1s gap) acts as a windowing function on the integral, preventing a single late shift from dominating the entire score.

The Displacement Vector Field

Consider every visible element as a point in 2D space. When a layout shift occurs, some subset of these points move. The distance fraction captures the magnitude of the largest displacement vector in that shift, normalized by the viewport diagonal. The impact fraction captures the area of influence — the union of the element's old and new bounding rectangles divided by viewport area.

This is structurally identical to computing work in physics: force (impact) multiplied by displacement (distance), summed over all events. CLS is the total work done by layout instability on the user's visual field.

// CLS session windows — a sliding integral
let sessionScores = [];
let currentWindow = { score: 0, start: 0, lastShift: 0 };

function onLayoutShift(entry) {
  const gap = entry.startTime - currentWindow.lastShift;
  const windowDuration = entry.startTime - currentWindow.start;

  // Window boundary: gap > 1s or duration > 5s
  if (gap > 1000 || windowDuration > 5000) {
    sessionScores.push(currentWindow.score);
    currentWindow = { score: 0, start: entry.startTime };
  }

  // Accumulate: ∫ IF × DF dt (discrete sum)
  currentWindow.score += entry.value;
  currentWindow.lastShift = entry.startTime;
}

// CLS = max over all session windows
const cls = Math.max(...sessionScores, currentWindow.score);
The session window trap CLS uses the maximum session window, not the sum of all windows. This means a page with one catastrophic 0.3 shift and ten benign 0.01 shifts scores 0.3 — not 0.4. Optimize the worst window first. The max operator here is analogous to the L∞ norm: the metric cares about your worst burst, not your average behavior.
04

INP — Latency Distributions & the Derivative of Frustration

Interaction to Next Paint replaced First Input Delay in March 2024, and the mathematical upgrade is significant. FID measured a single point — the delay of the first input event. INP measures the near-worst-case of all interactions: specifically, the interaction at the 98th percentile of the latency distribution (or the worst interaction, if fewer than 50 interactions occur).

INP ≈ F-1(0.98)
The inverse CDF (quantile function) of the interaction latency distribution, evaluated at p = 0.98

This is a quantile function — the inverse of the cumulative distribution function (CDF) of interaction latencies. If F(x) is the probability that a randomly selected interaction has latency ≤ x, then INP asks: "what latency threshold captures 98% of interactions?" This is fundamentally different from measuring means or even medians.

Why the 98th Percentile Is a Derivative Insight

User frustration doesn't scale linearly with latency. Research from Google's UX team and the original RAIL model suggests that frustration is closer to a step function: imperceptible below 100ms, mildly annoying from 100–200ms, and sharply negative above 200ms. If we model perceived frustration as F(d) where d is delay, the derivative F'(d) — the rate of change of frustration — spikes at the transition thresholds.

INP RangeRatingF'(d) RegimeUser Perception
≤ 200msGoodLow, near-zeroInstant — no conscious delay
200–500msNeeds WorkSteep, rapidly increasingSluggish — user notices lag
> 500msPoorSaturated plateauBroken — user assumes failure

The 200ms and 500ms thresholds aren't arbitrary — they correspond to inflection points on the frustration curve where F''(d) is at its maximum. Put differently, these are the points where the acceleration of frustration is highest. INP's thresholds are chosen at the points of maximum second derivative.

// Approximate INP: 98th percentile of interaction latencies
const latencies = [];

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // duration = input delay + processing + presentation
    latencies.push(entry.duration);
  }
}).observe({ type: 'event', durationThreshold: 16, buffered: true });

// F⁻¹(0.98) — quantile function
function getINP() {
  const sorted = latencies.slice().sort((a, b) => a - b);
  const idx = Math.floor(sorted.length * 0.98) - 1;
  return sorted[Math.max(0, idx)];
}
Practical takeaway INP is a tail-latency metric. Improving your median interaction latency from 80ms to 60ms does nothing if your worst interactions still spike to 600ms. Profile the outliers: long tasks on the main thread, forced synchronous layouts, and expensive event handlers that block the 98th percentile.
05

TTFB — Rate of Change at t = 0

Time to First Byte isn't technically a Core Web Vital, but it's the boundary condition that governs all others. Nothing can paint, shift, or respond to interaction until the first byte of the HTML response arrives. Mathematically, TTFB is the initial condition of the performance function — the value at t = 0 that constrains all subsequent behavior.

TTFB = tDNS + tTCP + tTLS + trequest + tserver
A sum of sequential latencies, each with its own probability distribution

What's interesting about TTFB from a calculus perspective is that it's a sum of random variables. Each component — DNS lookup, TCP handshake, TLS negotiation, request transmission, server processing — is itself a stochastic process with its own distribution. The total TTFB distribution is the convolution of these individual distributions.

The Convolution Insight

If DNS lookup time has distribution f₁(t) and TCP handshake has distribution f₂(t), the combined distribution of just those two steps is the convolution (f₁ * f₂)(t). The convolution of distributions is wider than either individual distribution — meaning variance compounds. This is why TTFB is so variable in the field even when each individual component seems stable: you're stacking five sources of variance, and the tails get fat.

The practical consequence: optimizing server processing time from 200ms to 150ms might be less impactful than eliminating one round trip from the chain entirely (e.g., via connection prewarming or a CDN edge that collapses DNS + TCP + TLS into a cached connection). Removing a term from the sum is always better than shrinking a term, because you eliminate both its mean and its variance contribution.

The compounding trap A 50ms DNS + 50ms TCP + 50ms TLS + 50ms server doesn't mean your P99 TTFB is 200ms. If each component has even modest variance (σ = 30ms), the combined P99 can exceed 400ms. Variance adds in quadrature: σtotal = √(σ₁² + σ₂² + σ₃² + σ₄²). Fewer steps in the chain beats faster steps.
06

The Performance Budget as Lagrangian Optimization

Here's where things get genuinely elegant. A performance budget is, at its core, a constrained optimization problem. You want to maximize feature richness (or some utility function U(features)) subject to the constraint that your Web Vitals stay within acceptable thresholds. This is precisely the setup for Lagrange multipliers.

maximize  U(x)   subject to   gLCP(x) ≤ 2.5s,  gCLS(x) ≤ 0.1,  gINP(x) ≤ 200ms
Where x is the vector of resource decisions (scripts, images, fonts, third-parties) and g(x) are the vital constraint functions

In this formulation, x is your resource allocation vector — every JavaScript bundle, image, font file, and third-party script is a dimension. The utility function captures business value: analytics, A/B testing, interactive features, rich media. The constraint functions map resource decisions to their Web Vital impact.

The Shadow Price of a Millisecond

The Lagrange multiplier λ for each constraint has a concrete interpretation: it's the shadow price of a millisecond. It tells you how much utility you sacrifice per unit of budget consumed on that vital. When λLCP is large, you're tight on your LCP budget and every additional resource that delays the largest contentful paint costs you disproportionately. When λINP is near zero, you have interaction headroom to spare.

This framework explains why "just remove the third-party scripts" is reductive advice. The right question isn't "how much does this script cost in milliseconds?" but "what's the marginal utility of this script relative to the shadow price of the binding constraint?" A 50ms script that powers your checkout flow has high utility relative to the same 50ms consumed by a social share widget. The Lagrangian makes that tradeoff explicit.

// Performance budget as constrained optimization
//
// Decision variables: x[i] ∈ {0, 1} for each resource i
// Utility:   U(x) = Σ value[i] × x[i]
// Constraints:
//   Σ lcp_cost[i] × x[i]  ≤  2500   (LCP budget, ms)
//   Σ cls_risk[i] × x[i]  ≤  0.1    (CLS budget)
//   Σ inp_cost[i] × x[i]  ≤  200    (INP budget, ms)
//
// This is a multidimensional knapsack problem.
// The Lagrangian relaxation gives approximate λ values
// that represent the "price per ms" on each constraint.
//
// High λ_LCP → you're LCP-constrained → prioritize render path
// High λ_INP → you're INP-constrained → prioritize main thread
// High λ_CLS → you're CLS-constrained → prioritize layout stability
Why this isn't just theory Lighthouse's performance scoring is already a weighted sum across metrics — a simplified utility function. The weights (LCP: 25%, TBT: 30%, CLS: 25%, FCP: 10%, SI: 10%) are Google's implicit Lagrange multipliers, expressing their model of which constraints matter most to user experience.
07

Putting It All Together

The calculus of Web Vitals isn't about pulling out a textbook and computing derivatives by hand. It's about recognizing that the structure of these metrics maps onto classical mathematical concepts — and that those concepts come with centuries of optimization intuition for free.

MetricCalculus ConceptOptimization Strategy
LCPSupremum of a set functionControl paint order; ensure the sup is reached early
CLSDefinite integral (max windowed)Minimize displacement magnitude; reduce window count
INPQuantile function (inverse CDF)Compress the tail of the distribution, not the median
TTFBConvolution of distributionsEliminate sequential steps; reduce variance at each stage
BudgetLagrangian optimizationAllocate resources by shadow price of binding constraint

The next time you're staring at a Lighthouse report, try seeing through the numbers to the functions beneath. LCP isn't a timestamp — it's the supremum of a growing set. CLS isn't a score — it's the integral of a displacement field. INP isn't a latency — it's a quantile on a distribution curve. And your performance budget isn't a spreadsheet — it's a Lagrangian with shadow prices attached to every constraint.

Once you see the math, you stop optimizing metrics and start optimizing the functions that generate them. And that's where the real gains live.

Further reading The Chrome team's web-vitals library (github.com/GoogleChrome/web-vitals) is the canonical implementation of these metrics. Read the source — particularly onCLS() and onINP() — and you'll see the session windows, percentile calculations, and supremum tracking discussed in this post implemented in ~200 lines of production JavaScript.