By Aditya Reddy
You record a slow click, change no code, click again, and the page's Interaction to Next Paint (INP) score drops. The slow interaction is still there. The calculation just picked a different one.
A dashboard gives you one number. A browser trace gives you a lot of events. I opened a PR to explain the INP calculation in the Event Timing specification, and it is still under review. Here is the calculation with actual numbers.
One click can leave several entries
Event Timing exposes PerformanceEventTiming entries. A gesture can produce several related entries, grouped by a nonzero interactionId. An entry with an ID of zero does not belong in this interaction calculation. The Event Timing specification describes that grouping.
Take these made-up entries from one page visit:
| Interaction ID | Event | Duration |
|---|---|---|
| 7 | pointerdown | 320 ms |
| 7 | pointerup | 296 ms |
| 7 | click | 280 ms |
| 14 | click | 480 ms |
| 21 | keydown | 240 ms |
Interaction 7 contributes 320 ms. Adding its three entries would produce 896 ms and count overlapping time more than once. Use the longest duration for the group.
That leaves three interaction latencies: 480, 320 and 240 ms, sorted from longest to shortest.
The fiftieth interaction changes the selection
INP leaves out one longest interaction for each complete group of 50 interactions. Google's INP explanation describes why pages with many interactions get this treatment.
Keep those three slow interactions, and suppose every other interaction takes less than 240 ms.
| Total interactions in the visit | Longest values skipped | Selected latency |
|---|---|---|
| 49 | 0 | 480 ms |
| 50 | 1 | 320 ms |
| 99 | 1 | 320 ms |
| 100 | 2 | 240 ms |
At the fiftieth interaction, a fast click can move the score from 480 to 320 ms. Nothing became faster.
The selection in web-vitals uses a descending list and a zero-based index:
const skip = Math.floor(interactionCount / 50); const index = Math.min(skip, descendingLatencies.length - 1); const candidate = descendingLatencies[index]; This fragment needs at least one candidate. It only covers selection. A complete INP collector needs more.
Keep interactionCount separate from your list length. The count covers interactions across the visit, though your collector may only receive and keep some of them. web-vitals keeps up to ten of the longest interactions.
Lowering the threshold does not recover the past
For entries of type event, Event Timing buffers and exposes durations of 104 ms or above by default. Registering an observer with durationThreshold: 16 lowers the threshold for subsequent delivery. Asking for buffered entries still only recovers the earlier events kept under the default threshold. The specification explains this distinction in when events are measured.
Fast interactions can therefore increase the total count without adding entries to your observed list. A late observer cannot reconstruct the missing durations just by asking for a lower threshold.
For production collection, start with web-vitals, which handles more than this selection step. First-input handling, page visibility and back/forward cache restores matter. A calculation for one window also cannot see every interaction inside an embedded frame.
Keep the score and the debugging evidence
When checking your calculation, keep the selected interaction's latency and ID alongside the total interaction count. Then you can see whether the interaction got faster or the calculation picked a different one.
If your fix is meant to speed up the 480 ms interaction, compare that interaction before and after the change under similar conditions. Another fast click can lower the visit score without fixing the slow interaction.
The proposed specification section covers grouping and selection, plus the observer threshold and the limit on retained candidates.

