Understanding INP (Interaction to Next Paint) in 2026
A deep technical breakdown of Google's Core Web Vitals metric INP, why it replaced FID, and how to identify and eradicate long tasks in JavaScript.
Why Interaction to Next Paint Replaced First Input Delay
In March 2024, Google officially transitioned from First Input Delay (FID) to Interaction to Next Paint (INP) as an official Core Web Vitals responsiveness metric. While FID merely measured the latency of the user's first interaction, INP measures the latency of all qualifying user interactions throughout the entire lifespan of the page visit.
INP records the worst (or near-worst 98th percentile) interaction latency, tracking three distinct sub-phases:
- Input Delay: Time spent waiting for main thread background tasks to clear before the event callback can start.
- Processing Time: The duration required to execute your JavaScript event listeners (
click,keydown,pointerdown). - Presentation Delay: The time the browser engine needs to recalculate layout, restyle the DOM, composit layers, and present the newly rendered frame to the physical display.
Understanding the 2026 INP Thresholds
- Good: Less than or equal to
200 ms. (Ensures UI feels instant and buttery smooth). - Needs Improvement: Between
200 msand500 ms. - Poor: Greater than
500 ms. (Results in search ranking penalties and visible user frustration).
Eradicating Long Tasks with scheduler.yield()
The number one cause of high INP is long JavaScript tasks (tasks exceeding 50ms) blocking the main browser thread. When a user clicks a button while a heavy calculation or JSON formatting is executing, the click event cannot be processed until the task completes.
Modern web browsers offer the scheduler.yield() API (with a fallback to setTimeout()) to yield control back to the rendering engine:
// Modern yield mechanism to keep main thread responsive
async function yieldToMain() {
if ('scheduler' in window && 'yield' in window.scheduler) {
return await window.scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
// Process large datasets without freezing UI
async function processLargeBatch(items) {
for (let i = 0; i < items.length; i++) {
doIntensiveWork(items[i]);
// Yield every 50 items to paint UI updates
if (i % 50 === 0) {
await yieldToMain();
}
}
}Visualizing Layout Thrashing
Another common culprit is interleaved DOM read and write operations. Reading layout metrics (like offsetHeight, scrollTop, getBoundingClientRect()) immediately after modifying styles forces synchronous layout recalculation. Always batch style reads first, then execute writes together.