INP: What It Means and Why It Matters
INP is the newest Core Web Vital. It measures how snappy your site feels — and most sites fail it because they have too much JavaScript.
Table of contents
- What INP measures
- The thresholds
- Why your INP is bad
- 1. Heavy event handlers
- 2. Third-party scripts
- 3. React (or any framework) re-rendering everything
- 4. Unoptimized search/filter inputs
- 5. Layout thrashing
- How to improve INP
- Fix 1: Break up long tasks
- Fix 2: Debounce expensive handlers
- Fix 3: Move work off the main thread
- Fix 4: Reduce React re-renders
- Fix 5: Audit and cut third-party scripts
- Fix 6: Use CSS for animations
- How to measure INP locally
- A realistic target
- The bottom line
INP — Interaction to Next Paint — is the newest Core Web Vital. It replaced First Input Delay (FID) in March 2024 because FID was too easy to pass. INP is a more honest measure of how snappy your site actually feels.
If your INP is bad, your site feels laggy. Period. Here's what it measures and how to fix it.
What INP measures
INP looks at every interaction on your page — taps, clicks, key presses — and measures how long it takes from the moment the user interacts until the next frame is painted.
It then reports the worst interaction (with some allowances for one-off outliers on long sessions). One slow interaction kills your INP score, even if everything else is snappy.
This is a deliberately strict measure. Real users remember the worst slowdowns, not the average.
The thresholds
| INP | Rating |
|---|---|
| ≤ 200ms | ✅ Good |
| 200–500ms | ⚠️ Needs improvement |
| > 500ms | ❌ Poor |
These thresholds are tight. A 500ms delay between tapping a button and seeing a response feels broken.
Why your INP is bad
The number one cause is JavaScript blocking the main thread. When the browser is busy running JavaScript, it can't respond to user input or paint new frames.
Specifically:
1. Heavy event handlers
A click handler that does too much work in a single function:
button.addEventListener("click", () => {
// Sort 10,000 items, update DOM, send analytics, log to console...
// All in one synchronous block.
});
2. Third-party scripts
Analytics, ad networks, tag managers, A/B testing tools — many of them install long-running handlers that fire on every interaction.
3. React (or any framework) re-rendering everything
A state change at the root of your tree can trigger thousands of components to re-render. Even with virtual DOM, that's not free.
4. Unoptimized search/filter inputs
A keypress that runs a search across 5000 items synchronously will block the main thread for hundreds of milliseconds.
5. Layout thrashing
JavaScript that reads layout properties (offsetHeight, getBoundingClientRect) and then writes back to the DOM in a loop forces the browser to recalculate layout repeatedly.
How to improve INP
Fix 1: Break up long tasks
The browser can respond to input between tasks, not during them. So splitting a 500ms task into ten 50ms tasks improves INP dramatically.
// ❌ Blocks for 500ms
function processAll(items) {
items.forEach(processItem);
}
// ✅ Yields between batches
async function processAll(items) {
for (let i = 0; i < items.length; i += 100) {
const batch = items.slice(i, i + 100);
batch.forEach(processItem);
await new Promise((r) => setTimeout(r, 0));
}
}
Modern browsers also support scheduler.yield() for explicit yielding to the event loop.
Fix 2: Debounce expensive handlers
Search-as-you-type, autocomplete, live filtering — anything that runs on every keystroke needs debouncing:
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
input.addEventListener("input", debounce(search, 200));
Fix 3: Move work off the main thread
CPU-heavy work (parsing large JSON, image processing, complex calculations) belongs in a Web Worker:
const worker = new Worker("/worker.js");
worker.postMessage({ data });
worker.onmessage = (e) => updateUI(e.data);
The main thread stays free for input.
Fix 4: Reduce React re-renders
- Use
React.memofor components that re-render unnecessarily. - Use
useMemoanduseCallbackfor expensive computations and stable references. - Avoid passing new object/array literals as props on every render.
- Split context to prevent global re-renders.
Fix 5: Audit and cut third-party scripts
Every third-party script runs on your main thread. Tag managers and analytics platforms are especially bad. See how JavaScript slows down websites for a detailed audit.
Fix 6: Use CSS for animations
transform and opacity are GPU-composited and don't block the main thread. Animating width, height, top, etc. triggers layout and paint, blocking interaction.
How to measure INP locally
INP requires real user interactions to measure properly. In Chrome DevTools:
- Open the Performance tab.
- Start recording.
- Interact with your page (click, scroll, type).
- Stop recording.
- Look at the "Interactions" lane — DevTools shows the time from input to next paint for every interaction.
You can also use the Web Vitals extension to see live INP as you browse.
For field data, PageSpeed Insights shows you real-user INP from Chrome's CrUX dataset.
A realistic target
Most well-built React apps land at 100–250ms INP. SaaS dashboards often hit 300–600ms because of constant data fetching and re-renders. Heavy editors (Figma, Notion) can be 500ms+ but those users tolerate it because the value is high.
For marketing sites and content pages, you should comfortably stay under 200ms.
The bottom line
INP is brutally honest about how snappy your site feels. The fix is almost always: less JavaScript, broken up into smaller chunks, with fewer third-party scripts and smarter event handlers.
For the broader Core Web Vitals story, see Core Web Vitals Explained for Beginners. For more on cutting JS, see how JavaScript slows down websites.
Frequently Asked Questions
What's a good INP score?+
200ms or less is good. 200–500ms needs improvement. Above 500ms is poor and means your site feels noticeably laggy.
How is INP different from FID?+
FID measured only the first interaction on a page. INP measures all interactions and reports the worst one. INP is harder to pass and more representative of real user experience.
Why is my INP bad even though my LCP is great?+
INP and LCP measure different things. LCP is about loading speed. INP is about responsiveness — usually killed by heavy JavaScript event handlers, third-party scripts, or React re-renders triggered by every interaction.
See how your site really performs
Run a full website health check on mobile and desktop in 30 seconds — no signup needed.