Interaction to Next Paint, or INP, measures how fast a page responds when someone clicks, taps, or types. It tracks every interaction through the whole visit and reports the worst latency, from the moment of input to the moment the screen visibly reacts. A good INP score is 200 milliseconds or less. INP replaced First Input Delay in Google's Core Web Vitals in March 2024, which makes it a ranking signal, and a page that fails it feels broken to users long before it loses positions. This post covers what drags INP down and how to fix it.
The short version
- Good is 200ms or less, poor is above 500ms, measured at the 75th percentile of real visits.
- INP replaced FID in March 2024 and measures all interactions, not just the first.
- Every interaction has three phases, which are input delay, processing, and presentation.
- The main enemy is long JavaScript tasks blocking the main thread.
The three phases of every interaction
- Input delay. The wait before the browser can even start handling the event, usually because JavaScript is hogging the main thread.
- Processing time. The time your event handlers spend running. Every millisecond of work in a click handler is a millisecond of INP.
- Presentation delay. The time to recalculate layout and paint the visual response. Large DOMs and heavy styles stretch this phase.
FID only ever measured the first of these, and only for the first interaction. INP measures all three for all of them, which is why it replaced FID and why some pages that used to pass now fail.
What causes poor INP
- Long JavaScript tasks. Anything over 50ms blocks the main thread and delays whatever the user does next.
- Heavy event handlers. Handlers that compute, manipulate the DOM, and fire analytics synchronously pile all of it into the latency.
- Large DOM size. More elements mean slower style and layout recalculation after every change. Aim for under about 1,500 elements.
- Layout thrashing. Alternating DOM reads and writes forces the browser to recalculate layout over and over inside one handler.
- Third-party scripts. Analytics, chat widgets, and ad tech compete for the same main thread your buttons need.
- Complex rendering. Filters, shadows, and huge repaint areas stretch the presentation phase.
How to fix Interaction to Next Paint
-
Break up long tasks
Split anything over 50ms into chunks and yield between them with scheduler.yield() or a setTimeout fallback, so pending input gets processed mid-work.
-
Keep event handlers lean
Show visual feedback immediately, then defer the heavy lifting. Users forgive work that takes a moment; they do not forgive a button that ignores them.
-
Defer non-critical work
Analytics and tracking belong in requestIdleCallback, not in the click path.
-
Shrink the DOM
Remove wrapper cruft, virtualize long lists, and lazy-render off-screen sections so every update has less to recalculate.
-
Stop layout thrashing
Batch all reads, then all writes. Reading offsetHeight between style writes forces a synchronous layout each time.
-
Contain your components
CSS contain and content-visibility tell the browser a component's changes stay inside it, cutting presentation delay.
-
Audit third-party scripts
Load them async or after first interaction, and drop the ones whose value does not survive the audit.
-
Fix the worst interaction first
INP reports your slowest moment, so one bad handler can fail the whole page. Field data tells you which one it is; start there.
The fixes in code
// Blocking: one long task, frozen page
items.forEach((item) => heavyProcessing(item));
// Responsive: yield between chunks
for (const item of items) {
heavyProcessing(item);
if (scheduler.yield) await scheduler.yield();
else await new Promise((r) => setTimeout(r, 0));
}
// Lean handler: feedback now, work deferred
button.addEventListener('click', () => {
button.classList.add('loading');
requestAnimationFrame(() => updateDOM(calculate()));
requestIdleCallback(() => sendAnalytics());
});
// Thrash-free: batch reads, then writes
const heights = els.map((el) => el.offsetHeight);
els.forEach((el, i) => { el.style.height = heights[i] + 10 + 'px'; }); Measuring INP
INP needs real interactions, so field data leads: PageSpeed Insights and Search Console both report it from the Chrome UX Report, which is the data Google ranks on. Lighthouse cannot measure INP in a lab run and reports Total Blocking Time as a stand-in. For debugging, record an interaction in the Chrome DevTools Performance panel and read the phase breakdown directly.
INP next to the other Core Web Vitals
| Metric | Measures | Good threshold | Primary fix |
|---|---|---|---|
| LCP | Loading speed | 2.5 seconds | Speed up the largest element |
| INP | Responsiveness | 200 milliseconds | Stop blocking the main thread |
| CLS | Visual stability | 0.1 | Reserve space for elements |
Of the three, INP usually takes the most work, because it touches code across the whole application rather than one loading path. Its stability sibling is the easier win, and our post on Cumulative Layout Shift covers those fixes.
Where Egochi fits
Egochi handles Core Web Vitals as part of our technical SEO services: finding the slow interactions in your field data, fixing the handlers and scripts behind them, and monitoring for regressions. Page experience is one signal among many, so that work runs inside our full SEO services rather than as a metric chased for its own sake. Fast pages are a means; rankings and revenue are the point.
Questions people ask about INP
What is a good INP score?
A good INP score is 200 milliseconds or less. Between 200 and 500ms needs improvement, and above 500ms is poor. Google measures the 75th percentile of real visits, so most of your interactions need to land under 200ms to pass.
How is INP different from FID?
FID measured only the first interaction, and only the delay before its handler started. INP measures every click, tap, and key press through the whole visit, covering input delay, processing, and rendering, then reports the worst latency. INP replaced FID in March 2024 because pages could pass FID and still feel slow.
Why is my INP score worse than my FID score was?
Because INP sees more. A page could pass FID while its handlers ran slowly or its later interactions lagged, since FID never looked at either. Nothing got slower when the metric changed; the measurement got honest.
Can I test INP in Lighthouse?
Not directly. INP needs real interactions, and a lab run has none, so Lighthouse reports Total Blocking Time as a proxy. For real INP, use PageSpeed Insights field data, Search Console, or in-browser measurement while you actually click around the page.
Which interactions count toward INP?
Clicks, taps, and key presses. Scrolling and hovering are excluded. Pages with many interactions report roughly the 98th percentile rather than the single worst, so one freak outlier does not define the score.
What is scheduler.yield()?
A browser API that lets a long JavaScript task hand control back to the main thread mid-run, so pending user input gets processed before the task continues. It is the cleanest way to break up heavy work, with a setTimeout fallback for browsers that lack it.