Engineering
Chasing INP: How We Debug Interaction Latency Now
First Input Delay was a polite metric. It measured only the delay before your first event handler ran, ignored everything after, and most sites passed it without trying. Interaction to Next Paint, which replaced it as a Core Web Vital this March, is not polite. It watches every click, tap, and keypress across the whole session and reports how long the page took to visibly respond. If your product feels sluggish, INP will say so, in public, in your search rankings.
We have now shipped INP work on four client properties since the changeover, and a pattern has emerged: teams that passed FID comfortably are failing INP by 200 milliseconds or more, almost always for the same three reasons.
Where the milliseconds actually go
The lab tells you very little here. INP failures live in field data, on mid-range Android phones, inside interactions your team never profiles because they feel fine on an M3 MacBook. Our debugging loop starts with the real users:
- Pull the slowest interactions from RUM data, grouped by element selector, not by page. The offender is usually one component, not one route.
- Reproduce on a throttled device profile, 6x CPU slowdown minimum. If you cannot feel the problem, you cannot fix it.
- Record a performance trace and read the main thread during the interaction, not during load.
Nine times out of ten the trace shows one of these: a state update that re-renders half the component tree, a synchronous layout read inside a handler, or third-party script work that happens to land in the same frame as the user's tap.
Yield early, render less
The single most effective fix is embarrassingly simple: do the visual response first, then do the work. The browser cannot paint until your handler yields, so yield.
button.addEventListener("click", async () => {
setPending(true); // cheap visual response
await scheduler.yield?.() ?? // let the browser paint
new Promise(r => setTimeout(r));
runExpensiveFiltering(); // the real work, one frame later
});
That one frame of separation routinely cuts an interaction from 450 milliseconds to under 150, and users read the pending state as instant. React teams get the same effect from transitions: wrap the expensive state update in startTransition and keep the urgent update, the one the user is staring at, synchronous.
The second fix is rendering less. We audit every list the interaction touches. If a filter click re-renders 400 product cards to change 12, that is the bug, and no amount of scheduling cleverness will save you. Memoize, virtualize, or paginate, in that order of preference.
Third parties are your problem now
The uncomfortable part of INP is that it charges you for scripts you did not write. Tag managers, chat widgets, and consent platforms all run on your main thread, and their timers land wherever they land, including inside your user's tap. We now put third-party main thread time into the performance budget with a hard ceiling, and we load anything nonessential after first interaction rather than after load. If a vendor cannot survive that, the vendor is telling you something.
Make it a habit, not a fire drill
INP regressions creep in one feature at a time, so we wired the checks into the release process: a scripted interaction test in CI on a throttled profile, plus a weekly look at field percentiles. Fifteen minutes a week. The alternative is discovering the regression in Search Console three weeks after it shipped, which is how these things usually get discovered.
The metric change is good for the web, even if it is briefly painful for teams. FID let everyone pretend responsiveness was solved. INP measures what users actually feel, and what users feel is the product.
Building something this could apply to?
We take on a small number of flagship projects each quarter.
Start a project