INP (Interaction to Next Paint) measures a web page’s responsiveness to user interactions. This article demystifies Google’s Core Web Vitals metric, explores how it differs from the deprecated FID metric, and outlines how to optimize your JavaScript execution pathways to achieve “Good” scores.
1. What is Interaction to Next Paint (INP)?
INP tracks the latency of all user interactions (clicks, taps, and keyboard inputs) during a user’s visit to a page. It measures the duration between when a user initiates an action and when the browser paints the next frame showing the visual update.
How it Differs from FID (First Input Delay)
FID was Google’s previous response metric, but it had limitations that made it easy to bypass:
- First-Interaction Bias: FID only monitored the very first user interaction. Any subsequent delay caused by navigating menus or clicking filters was ignored.
- Ignored Script Execution: FID only calculated the input delay (how long the browser took to receive the event). It ignored the time required to execute the actual JavaScript event handler and paint the updated UI.
INP addresses these limitations by tracking all interactions across the entire lifecycle of the session, reporting the worst-performing interaction (excluding outliers).
INP Scoring Thresholds
- Good (Pass): 200ms or less
- Needs Improvement: 200ms to 500ms
- Poor: Over 500ms
2. Key Causes of Poor INP Scores
The core culprit behind poor INP scores is Main Thread Blocking.
Because browsers handle UI rendering and JavaScript execution on a single main thread, launching complex script logic instantly blocks the browser from updating the viewport. If a click triggers a task taking 300ms, the screen freezes during that window, raising your INP score.
3. Techniques to Optimize INP
1) Task Yielding (Breaking up Long Tasks)
Any JavaScript function that runs for more than 50ms is flagged as a “Long Task.” To resolve this, break long-running tasks into modular steps and yield the main thread back to the browser to process UI frames.
// ✕ Bad: Continuous loop blocking the main thread
function processArray(items) {
for (let item of items) {
calculateComplexResult(item); // Freezes the browser
}
}
// ◯ Good: Yielding control periodically using async timeouts
async function processArrayOptimized(items) {
for (let i = 0; i < items.length; i++) {
calculateComplexResult(items[i]);
// Yield every 50 items to let the browser paint
if (i % 50 === 0) {
await yieldToMainThread();
}
}
}
function yieldToMainThread() {
// Use scheduler.yield if supported, otherwise fallback to setTimeout
if ('scheduler' in window && 'yield' in window.scheduler) {
return window.scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}
2) Optimizing User Feedback Loop
If you must run heavy logic, ensure that you update the UI with a “loading spinner” or active button state before the heavy processing script begins.
button.addEventListener('click', () => {
// Step 1: Update UI immediately (registers low INP latency)
toggleSpinner(true);
// Step 2: Queue the heavy calculation task for the next event loop
setTimeout(() => {
executeHeavyCalculation();
toggleSpinner(false);
}, 0);
});
4. Conclusion
Optimizing for INP is not just about check-marking SEO audits. Providing immediate visual feedback on user inputs increases conversion rates and stops users from abandoning unresponsive pages. Use Chrome DevTools’ “Performance” panel to track down long tasks and optimize your user experiences.
