INP: Interaction Responsiveness & Long Task Attribution
INP is not a first-impression metric — it measures the worst interaction latency across the entire session, which means a single 500ms click handler that runs once per session can fail INP for every user who triggers it.
Modern Web Rendering & Performance
INP: Interaction Responsiveness & Long Task Attribution
INP is not a first-impression metric. LCP measures what the user sees when the page first loads. INP measures what happens every time they touch your application — every click, every keypress, every tap — for the entire session. And it reports the 98th percentile of all those interactions. One button click handler that takes 600ms to complete, triggered once in a user session by 5% of your users, fails INP for those users. There is no averaging it away with fast interactions. There is no burying it in low-traffic pages. The scheduler sees every interaction and records the worst one.
1. The Broken Pattern: Measuring the Wrong Thing
Engineers see 350ms in the console and conclude the interaction is within budget. The browser scores the same interaction at 620ms and fails INP. The missing 270ms is invisible to console.time — it lives in the Input Delay and Presentation Delay phases that bracket the handler execution.
2. INP Defined: Three Phases, One Score
INP is the 98th percentile of all interaction duration scores across a user session. Each interaction duration spans three phases:
Thresholds:
| Score | Threshold |
|---|---|
| ✅ Good | ≤ 200ms |
| ⚠️ Needs Improvement | ≤ 500ms |
| ❌ Poor | > 500ms |
3. Why INP Replaced FID
FID (First Input Delay) was deprecated as a Core Web Vital in March 2024. The replacement is INP. Understanding why reveals what INP measures that FID missed:
| Property | FID | INP |
|---|---|---|
| Which interactions | First user interaction only | All interactions in the session |
| What is measured | Input Delay phase only | Input Delay + Processing Time + Presentation Delay |
| Aggregation | Single value (first event) | 98th percentile (worst) |
| Threat model | Slow initial page load blocking the first click | Slow event handlers anywhere in the app at any time |
FID missed what developers call the "second mountain" — applications that load fast but slow down after user interactions (state updates, client-side data fetches, re-renders). A heavy onClick handler on a product page never affects FID if the user clicks the button after the page has hydrated. INP catches it every time.
4. Long Animation Frames (LoAF)
The Long Animation Frames API (LoAF) identifies animation frames that took more than 50ms and attributes them to the scripts that caused the delay. Use PerformanceObserver with type: 'long-animation-frame'.
Browser support: LoAF is supported in Chrome 123+ and Edge 123+ only as of 2026. Firefox and Safari do not support it. Always add a feature-detection guard — do not assume the observer registers in all browsers.
LoAF's scripts array is the critical diagnostic tool: it tells you not just that a frame was slow, but which event-listener at which source location caused it. Compare this to the deprecated Long Tasks API, which could only report that some long task occurred — without identifying which script.
5. Breaking Up Long Tasks: scheduler.yield() vs. the Alternatives
When a long task runs, it blocks the main thread — input events cannot fire, rendering cannot occur, and INP accumulates. The solution is to break the task into smaller chunks with explicit yield points.
5.1 The Options and Their Behavior
scheduler.yield() browser support: Shipped in Chrome 129+, Edge 129+, and Firefox 142+. Safari does not yet support it. Always add a feature-detect fallback for Safari and older browsers.
| Mechanism | Yields to input events? | Priority-aware? | Reliable timing? |
|---|---|---|---|
scheduler.yield() |
✅ Yes | ✅ Yes | ✅ Yes |
requestIdleCallback |
❌ No | ❌ No | ⚠️ Throttled |
setTimeout(fn, 0) |
✅ Yes | ❌ No | ⚠️ Throttled on inactive |
queueMicrotask |
❌ No (runs in same task) | ❌ No | — |
5.2 Debouncing vs. Yielding — A False Equivalence
Debouncing and yielding solve different problems and are not interchangeable:
Debounce and throttle are input-rate controls — they reduce the frequency of function calls. They do not help INP when the underlying function is itself a long-running task. Use debounce/throttle to reduce unnecessary calls, then use scheduler.yield() to break up the calls that do execute.
6. startTransition and useDeferredValue as INP Tools
React's concurrent rendering features integrate with the browser's input event priority to reduce INP from heavy state updates.
useDeferredValue causes a double render on every deferred update — first with the old value (immediate, cheap), then with the new value (deferred, expensive). If the deferred render is still expensive after useDeferredValue, the double render adds to Processing Time and can worsen INP. Use useDeferredValue only when the deferred render is fast (< 50ms) and the primary benefit is input responsiveness during rapid keystrokes.
7. Diagnosing INP in Production
Triage priority based on the attribution breakdown:
| Dominant phase | Root cause | Fix |
|---|---|---|
| Input Delay > 100ms | Long task running at moment of input | Reduce long tasks outside handlers (analytics, resize, scroll) |
| Processing Time > 100ms | Slow event handler | Yield with scheduler.yield(); move heavy work off main thread |
| Presentation Delay > 100ms | ResizeObserver/MutationObserver after handler | Defer observer callbacks; avoid forced layout |
Summary
| Concept | Rule |
|---|---|
| INP definition | 98th percentile of all interaction durations (Input Delay + Processing Time + Presentation Delay) across the entire session. |
| Why it replaced FID | FID measured only the first interaction's input delay. INP measures every interaction's full three-phase duration. |
scheduler.yield() |
The correct API for breaking long tasks to yield to input events. requestIdleCallback does not yield to input events and does not help INP. |
| Debounce vs. yield | Debounce reduces call frequency. Yield breaks running task duration. Use both, for different problems. |
startTransition |
Marks state updates as non-urgent — React can pause and resume them around incoming input events. |
useDeferredValue |
Causes a double render. Only beneficial when the deferred render is fast (<50ms) and rapid keystroke input responsiveness is the goal. |
What's Next
In Part 8, we address CLS — Cumulative Layout Shift. The most misunderstood Core Web Vital: the fix is not
widthandheightattributes. It is giving the browser's layout engine a geometry reservation before paint, and knowing thattransformanimations never cause CLS whiletop/left/marginalways do. Part 8: CLS →
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.