In March 2024, Google replaced First Input Delay (FID) with Interaction to Next Paint (INP) as a Core Web Vital. This shift reflects a more comprehensive approach to measuring responsiveness. While FID only measured the delay before the browser could respond to an input, INP measures the entire interaction lifecycle from user input to the next visual update.
The impact is significant: 43% of websites fail the INP threshold, making it a critical metric for SEO and user experience. This article explains what INP measures, why it matters, and how to optimize your site's INP score with practical code examples.
Understanding INP: What It Actually Measures
INP measures the responsiveness of a page by observing all user interactions throughout the page's lifecycle. It captures the latency of click, tap, and keyboard interactions, reporting the worst interaction (or near-worst) as the page's INP score.
"INP is not about a single interaction. It's about the overall responsiveness of your page. Google looks at all interactions and reports the 98th percentile, meaning it captures the worst experiences users are having."
The INP metric has three thresholds:
- Good: Less than 200ms
- Needs improvement: 200-500ms
- Poor: More than 500ms
Unlike FID, which only measured the first interaction on a page, INP considers all interactions throughout the user's session. This makes it a more accurate representation of actual user experience.
Why INP Replaced FID
FID had several limitations that INP addresses:
- Single interaction focus: FID only measured the first interaction, missing responsiveness issues that occurred later in the session
- Limited input types: FID only measured certain input types, ignoring others like keyboard events
- Partial measurement: FID only measured input delay, not the full interaction lifecycle
INP solves these by measuring the full interaction latency for all input types throughout the entire page lifecycle. This provides a more complete picture of page responsiveness.
Common Causes of Poor INP Scores
Based on our analysis at Curious Kaizer, the most common causes of poor INP scores are:
1. Long Event Handlers
JavaScript event handlers that execute too much work on the main thread block the browser from responding to user input. This is the most common cause of poor INP scores.
2. Heavy JavaScript Execution
Large JavaScript bundles that take significant time to parse and execute can delay interactions. This is particularly problematic on mobile devices with less powerful CPUs.
3. Layout Shifts During Interaction
Layout shifts that occur during user interaction can extend the time until the next paint, increasing INP scores.
4. Third-Party Scripts
Analytics, advertising, and tracking scripts that execute on the main thread can block interactions and increase INP scores.
Practical Optimization Strategies
1. Break Up Long Tasks
Use requestIdleCallback or setTimeout to break up long-running JavaScript tasks into smaller chunks:
// Instead of blocking the main thread
function processLargeDataset(data) {
// This blocks the main thread
data.forEach(item => {
heavyProcessing(item);
});
}
// Break it up into smaller chunks
function processLargeDataset(data) {
let index = 0;
const chunkSize = 100;
function processChunk() {
const chunk = data.slice(index, index + chunkSize);
chunk.forEach(item => heavyProcessing(item));
index += chunkSize;
if (index < data.length) {
requestIdleCallback(processChunk);
}
}
processChunk();
}
2. Use Web Workers for Heavy Computation
Move CPU-intensive work to Web Workers to keep the main thread free for user interactions:
// main.js
const worker = new Worker('heavy-computation.js');
button.addEventListener('click', () => {
// Send data to worker instead of processing on main thread
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
// Update UI when worker is done
updateResults(e.data);
};
});
// heavy-computation.js
self.onmessage = (e) => {
const result = heavyProcessing(e.data.data);
self.postMessage(result);
};
3. Optimize Event Handlers
Debounce and throttle event handlers to prevent excessive execution:
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Use debounced handler
const debouncedHandler = debounce((e) => {
heavyProcessing(e.target.value);
}, 300);
input.addEventListener('input', debouncedHandler);
4. Reduce JavaScript Bundle Size
Use code splitting and tree shaking to reduce the amount of JavaScript that needs to be parsed and executed:
// Dynamic import for code splitting
button.addEventListener('click', async () => {
const { heavyModule } = await import('./heavy-module.js');
heavyModule.process();
});
// Tree shaking with ES modules
// Only export what you use
export const usefulFunction = () => { /* ... */ };
// Dead code will be removed by bundlers
5. Optimize Third-Party Scripts
Load third-party scripts asynchronously and use defer to prevent blocking:
<!-- Load scripts asynchronously -->
<script async src="analytics.js"></script>
<script defer src="tracking.js"></script>
<!-- Or load dynamically -->
<script>
window.addEventListener('load', () => {
const script = document.createElement('script');
script.src = 'analytics.js';
script.async = true;
document.head.appendChild(script);
});
</script>
Measuring INP in Your Application
Use the Web Vitals library to measure INP in production:
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric);
// Send to analytics
analytics.track('INP', {
value: metric.value,
rating: metric.rating,
id: metric.id
});
});
For debugging, use Chrome DevTools Performance panel to identify long tasks and interaction delays:
- Open Chrome DevTools (F12)
- Go to the Performance tab
- Record user interactions
- Look for long tasks (red bars in the timeline)
- Identify which functions are causing delays
Key Takeaways
Optimizing for INP requires a systematic approach to JavaScript performance:
- Measure first - Use Web Vitals library to establish a baseline and track improvements
- Break up long tasks - Use requestIdleCallback or setTimeout to prevent main thread blocking
- Use Web Workers - Move CPU-intensive work off the main thread
- Optimize event handlers - Debounce and throttle to prevent excessive execution
- Reduce bundle size - Use code splitting and tree shaking to minimize JavaScript
- Control third-party scripts - Load asynchronously and defer when possible
INP is now a ranking factor, so optimizing for it directly impacts your SEO performance. More importantly, better INP scores mean a more responsive user experience, which leads to higher engagement and conversion rates.