Skip to content
QSWEQB
hardConceptScenarioMidSeniorStaff

Field data says your page has an INP of 400 ms. How do you find the cause and fix it?

Split the interaction into input delay, processing time and presentation delay, then attribute a real interaction from the field rather than guessing. Most of the budget is usually spent before your handler runs or after it returns, so yielding and cutting rendering cost beats optimising the handler.

4 min readUpdated 2026-07-26

What the interviewer is scoring

  • Does the candidate decompose the interaction into its three spans before naming a fix
  • Whether they know INP reports a near-worst interaction per visit rather than an average
  • That they look for what was already occupying the main thread, not only at the handler code
  • Whether yielding is described as breaking a task into separate tasks, with the paint opportunity that creates
  • Can they say where INP data does not exist and what that omits from the picture

Answer

What the number is counting

INP replaced First Input Delay as a Core Web Vital in March 2024, and the change was not cosmetic. FID measured only the delay before the first interaction's handler began. INP measures the full duration from a user's input until the next frame is painted showing the result, for every click, tap and key press on the page, and reports approximately the worst of them for the visit — on busy pages one interaction in fifty is discarded so a single outlier cannot define the score. Good is 200 ms or under; over 500 ms is poor. Scrolling and hovering are not interactions for this purpose.

That "until the next paint" clause is what makes INP hard, because it holds you responsible for three spans and only the middle one is your event handler:

  • Input delay — the gap between the user acting and the handler starting, spent waiting for the main thread to be free.
  • Processing time — your handlers, all of them registered for that event, running to completion.
  • Presentation delay — the work between the handler returning and the frame being painted: style recalculation, layout, paint, plus any other tasks that queued ahead of the render.

Attribute a real interaction before touching code

Do not begin in a profiler on your own machine, because the interaction that is slow in the field is frequently not the one you would have chosen and the conditions that make it slow are not present locally. Get the breakdown from real users first. The web-vitals library reports INP with attribution: which element was interacted with, which event type, and how the duration split across the three spans. That single fact — which span dominates — determines which of three unrelated fixes you need.

In Chromium you can go further with the Long Animation Frames API, available since Chrome 123, which reports frames that took over 50 ms along with the scripts that ran inside them and the source location of each. That is what turns "something blocked the main thread" into a file and a line, including for third-party code.

When the delay comes first

A dominant input delay means the thread was busy when the user acted, so nothing about your handler is the problem. The usual culprits are hydration, a large bundle being parsed and executed, a data-layer or tag-manager script initialising, or your own long task doing eager work on load. The fix is scheduling, not micro-optimisation: defer non-critical scripts, split hydration so it happens in pieces, and load third-party tags after the interactive window rather than during it.

When the handler is long, break the task rather than the work

A single task holding the thread for 300 ms is 300 ms during which no input can be processed and no frame can be painted. Cutting the work in half helps half as much as you would like; yielding helps more, because it gives the browser a chance to paint and to run pending input between the pieces.

// Yields to the browser between chunks. Continuation is prioritised
// ahead of newly queued tasks, unlike a setTimeout of zero.
async function render(rows) {
  for (let i = 0; i < rows.length; i += 50) {
    paintChunk(rows.slice(i, i + 50));
    if (i + 50 < rows.length) await scheduler.yield();
  }
}

scheduler.yield() shipped in Chrome 129 and is not yet available everywhere, so feature-detect and fall back to await new Promise(r => setTimeout(r, 0)). The fallback yields, but the continuation joins the back of the task queue, so on a busy thread it can be starved by work queued after it — which is exactly the difference the newer API exists to fix. Separately, if the visual response and the bookkeeping are different jobs, do the visual part in the handler and push the analytics, logging and state persistence past the yield. The user is waiting for the checkbox to tick, not for the event to be recorded.

Presentation delay is a rendering problem

If the handler returns quickly and the frame still arrives late, you are paying for the DOM change itself. A click that toggles a class on an ancestor of a large tree forces style recalculation and layout across all of it. Interleaving reads and writes to layout properties inside the handler makes it worse by forcing synchronous layout mid-task. content-visibility: auto on off-screen sections lets the browser skip their layout and paint entirely, and reducing the number of elements affected by the change beats making each one cheaper.

The part of the picture you do not have

INP is derived from the Event Timing API, which Chromium implements and Safari and Firefox do not. Field INP, and therefore the Chrome UX Report, describes Chromium users only. On an iOS-heavy audience a large share of your traffic contributes nothing to the metric while still experiencing whatever the metric was trying to catch, and WebKit's own interaction costs differ. Saying that unprompted signals you understand the measurement rather than only the target.

Ask which of the three spans is largest before you propose anything; two of the three are fixed by scheduling and rendering work that has nothing to do with the handler everyone starts reading.

Likely follow-ups

  • Input delay dominates and your handler is two lines long. What is on the main thread and how do you find out?
  • Why can moving work into a requestAnimationFrame callback make presentation delay worse?
  • What is the difference between awaiting setTimeout zero and calling scheduler.yield, and when does it matter?
  • How would you attribute a bad INP to a third-party script you do not control?

Related questions

Further reading

core-web-vitalsinplong-tasksevent-loopscheduling