A website can load quickly and still feel slow to use. A user clicks a button, opens a menu, or selects a filter, but nothing happens immediately. The page eventually responds, yet the delay makes the application feel unresponsive.
This problem is common in web applications that perform expensive JavaScript operations, render large component trees, or run too much work on the main thread.
Interaction to Next Paint (INP) helps developers measure this experience. It is a Core Web Vital that evaluates how quickly a page responds visually to user interactions throughout a visit.
Understanding INP helps developers identify slow interactions, investigate the JavaScript responsible, and improve the experience for real users.
In this article, you will learn what INP measures, how to diagnose poor results, how to use browser performance tools, and how to improve interaction responsiveness in production applications.
What Is Interaction to Next Paint (INP)?
Interaction to Next Paint, or INP, measures the latency of user interactions with a web page. It considers interactions such as clicks, taps, and keyboard input and evaluates the delay until the browser presents the next visual update.
INP became a Core Web Vital in March 2024, replacing First Input Delay (FID).
The difference matters because FID measured only the delay before a browser began processing the first qualifying interaction. INP considers interaction responsiveness across the page visit, providing a broader view of how the interface behaves.
For example, imagine a dashboard with several actions:
- Opening a navigation menu.
- Applying filters to a report.
- Expanding a table row.
- Submitting a search form.
- Switching between tabs.
If most actions respond quickly but applying a filter takes several hundred milliseconds, INP may expose that problem.
INP is not simply the time required to complete a network request or finish an application operation. It focuses on the interaction's responsiveness, including the delay before processing, the time spent processing event handlers, and the delay before the browser presents the next frame.
What Is a Good INP Score?
Google's Core Web Vitals guidance defines the following INP thresholds:
| INP value | Assessment |
|---|
| 200 milliseconds or less | Good |
| More than 200 milliseconds and up to 500 milliseconds | Needs improvement |
| More than 500 milliseconds | Poor |
These thresholds should be evaluated at the 75th percentile of page visits, commonly referred to as p75. Results should be considered separately for mobile and desktop users when sufficient data is available.
For example, suppose your application has an INP p75 of 180 milliseconds on desktop but 320 milliseconds on mobile. Desktop performance is in the good range, while mobile performance needs improvement.
This difference may result from slower devices, larger JavaScript workloads, or different interaction patterns.
The goal is not to optimize one test run until it produces a low number. It is to improve responsiveness for real users across a representative set of visits.
How INP Measures an Interaction
INP reflects three main stages of an interaction.
1. Input Delay
Input delay is the time between the user interaction and the point when the browser begins processing its event handlers.
Long-running JavaScript tasks can prevent the browser from responding promptly.
For example:
function handleSearchClick() {
performExpensiveCalculation();
updateSearchResults();
}
If performExpensiveCalculation() blocks the main thread, the browser may have to wait before processing the click.
2. Processing Time
Processing time covers the execution of event handlers associated with the interaction.
An event handler that performs complex calculations, filters a large array, or updates many components can increase this part of the latency.
For example:
button.addEventListener("click", () => {
const filteredProducts = products.filter(product => {
return product.price > 1000;
});
renderProducts(filteredProducts);
});
Filtering a small array may be inexpensive. Filtering a very large collection and rendering every result can take significantly longer.
3. Presentation Delay
Presentation delay is the time between the completion of the interaction's event handlers and the next visual update being presented.
The browser may need to perform style calculations, layout, paint, and other rendering work before displaying the result.
For example, changing the visibility of one small element may be inexpensive. Updating thousands of DOM nodes can require substantially more work.
These stages explain why reducing event-handler execution time alone may not fix poor INP.
Step 1: Measure INP in the Browser
Before changing code, determine whether the problem affects real users and identify the interactions that contribute to it.
Start with Chrome DevTools.
Open your application in Chrome, press F12, and select the Performance panel. Record a session while performing the interaction that feels slow.
For example:
- Open a data-heavy dashboard.
- Start recording in the Performance panel.
- Click the report filter.
- Wait for the interface to update.
- Stop recording.
- Inspect the main thread and related rendering activity.
Look for long tasks, expensive event handlers, repeated layout calculations, and large rendering operations around the interaction.
A long task is generally a task that occupies the main thread for more than 50 milliseconds. Long tasks can delay input processing and rendering, although a task exceeding 50 milliseconds does not automatically mean that an interaction will have poor INP.
For production monitoring, collect field data where possible. Laboratory measurements help reproduce problems, but real-user measurements show how the application behaves across actual devices and network conditions.
Step 2: Collect INP Data From Real Users
The web-vitals JavaScript library can report Core Web Vitals measurements, including INP where supported.
Install the package:
npm install web-vitals
Then register an INP callback:
import { onINP } from "web-vitals";
onINP((metric) => {
console.log("INP:", metric.value);
console.log("Rating:", metric.rating);
});
This example logs the metric value and rating. It is useful during development, but production monitoring should send the measurements to an approved analytics or observability endpoint.
For example, a reporting function could look like this:
import { onINP } from "web-vitals";
function sendToAnalytics(metric) {
const payload = {
name: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id
};
navigator.sendBeacon(
"/performance-metrics",
JSON.stringify(payload)
);
}
onINP(sendToAnalytics);
The /performance-metrics endpoint is an example route that your application must implement. The server should validate incoming data, apply appropriate rate limits, and avoid collecting unnecessary personal information.
The sendBeacon() call is intended to send small amounts of data without blocking page navigation. It does not guarantee that the server will successfully process every report.
For production use, follow the library's current reporting guidance and verify the metric's lifecycle, attribution options, and browser support. A single console measurement is not a substitute for collecting enough field data to calculate a reliable p75.
Step 3: Find the Interaction That Causes the Delay
Knowing that a page has poor INP is only the first step. You need to identify the interaction responsible.
Suppose a product page has three actions:
| Interaction | Observed latency |
|---|
| Open navigation menu | 70 ms |
| Expand product details | 110 ms |
| Apply product filters | 420 ms |
In this example, applying filters is the most obvious candidate for investigation.
The delay might come from several sources:
- Filtering a large dataset on the main thread.
- Re-rendering too many components.
- Updating the DOM repeatedly inside a loop.
- Running synchronous calculations in an event handler.
- Recalculating expensive derived state.
- Waiting for rendering work to finish after the handler returns.
Use browser performance recordings to distinguish these causes.
If event processing is expensive, inspect the handler and its dependencies. If the handler finishes quickly but the next frame is delayed, investigate style recalculation, layout, painting, and component rendering.
Avoid guessing based only on the visible delay. The same user experience can have very different technical causes.
Step 4: Break Up Long JavaScript Tasks
One common INP problem is performing too much synchronous work in a single event handler.
Consider this example:
function processRecords(records) {
const results = records.map(record => {
return expensiveCalculation(record);
});
renderResults(results);
}
If the collection is large, this function can block the main thread until the calculations finish.
One improvement is to divide the work into smaller batches and allow the browser to process other tasks between them.
function yieldToBrowser() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function processRecords(records) {
const results = [];
const batchSize = 100;
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
for (const record of batch) {
results.push(expensiveCalculation(record));
}
await yieldToBrowser();
}
renderResults(results);
}
This example yields between batches, allowing the browser an opportunity to process other work.
However, setTimeout() does not guarantee an immediate yield or a fixed execution time. The total computation still has to happen, and renderResults() may remain expensive.
For CPU-intensive calculations, a Web Worker can move work away from the main thread. For large UI updates, rendering fewer elements or using list virtualization may provide a greater improvement than simply dividing the calculation into batches.
Choose the approach based on the performance recording and the type of work being performed.
Step 5: Reduce Unnecessary Rendering
Frontend frameworks can trigger expensive rendering when state changes affect large component trees.
Consider a React component that filters and renders thousands of products every time the user types into a search field.
function ProductList({ products, searchTerm }) {
const filteredProducts = products.filter(product =>
product.name
.toLowerCase()
.includes(searchTerm.toLowerCase())
);
return (
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
This example is straightforward, but it may become expensive when the collection is large or the filtering logic is complex.
Possible improvements include:
- Keeping state close to the components that need it.
- Avoiding unnecessary component updates.
- Memoizing expensive derived calculations when profiling shows a benefit.
- Rendering only visible rows for very large lists.
- Debouncing operations that do not need to run on every keystroke.
- Separating expensive calculations from urgent interface updates.
Memoization is not always the right solution. It adds complexity and may not help if the underlying work is inexpensive. Measure before introducing it.
Step 6: Keep the Interface Responsive During Expensive Work
Sometimes an interaction triggers an operation that cannot finish immediately, such as searching a large dataset or loading results from a server.
The interface should acknowledge the user's action promptly.
For example, a search button can show a loading state while the application performs the operation.
async function searchProducts(query) {
const button = document.querySelector("#search-button");
const status = document.querySelector("#search-status");
button.disabled = true;
status.textContent = "Searching...";
try {
const response = await fetch(
`/api/products?q=${encodeURIComponent(query)}`
);
if (!response.ok) {
throw new Error("Unable to load products");
}
const products = await response.json();
renderProducts(products);
status.textContent = `${products.length} products found`;
} catch (error) {
status.textContent = "Search failed. Please try again.";
} finally {
button.disabled = false;
}
}
The example provides immediate feedback and handles failed requests.
A network request that yields control to the browser is generally preferable to a synchronous operation that blocks the main thread. However, the code still needs to render the results efficiently. If renderProducts() creates thousands of DOM nodes at once, presentation delay may remain high.
Also, visual feedback should not be confused with making the underlying operation finish faster. It improves the user's understanding of what is happening, while actual INP improvements depend on how the interaction and subsequent rendering are handled.
Common INP Problems and Their Solutions
| Problem | Likely cause | Possible improvement |
|---|
| Buttons respond slowly | Long-running event handlers | Reduce synchronous work or split it into smaller tasks |
| Filters freeze the page | Large calculations or excessive rendering | Optimize filtering and render fewer elements |
| Menus open with a delay | Main-thread contention or expensive layout | Inspect event handlers and rendering activity |
| UI changes appear late | Heavy DOM updates or style recalculation | Reduce DOM work and simplify layout changes |
| Mobile INP is worse than desktop | Limited CPU resources or heavier mobile workloads | Test on representative mobile devices and optimize the critical interaction path |
| INP varies between visits | Different interaction paths or unpredictable page workloads | Collect field data and investigate representative sessions |
Best Practices for Improving INP
- Measure field performance before prioritizing work.
- Use Chrome DevTools to identify the specific slow interaction.
- Keep event handlers short and avoid expensive synchronous operations.
- Break up long tasks or move CPU-intensive calculations to Web Workers.
- Reduce unnecessary rendering and DOM updates.
- Use list virtualization for large collections.
- Provide prompt visual feedback for operations that take time.
- Test on representative mobile devices as well as desktop browsers.
- Recheck field data after deploying improvements.
- Treat INP as one part of the broader Core Web Vitals assessment.
Advantages and Limitations of INP
| Advantages | Limitations |
|---|
| Reflects responsiveness across a page visit | Requires enough field data for reliable conclusions |
| Helps identify interaction problems that page-load metrics miss | Does not directly explain the cause of a delay |
| Supports mobile and desktop performance analysis | Results depend on the interactions users perform |
| Encourages developers to investigate main-thread and rendering work | Laboratory tests may not reproduce real-user conditions |
| Provides a standardized Core Web Vital target | Improving INP does not automatically improve every aspect of user experience |
Summary
Interaction to Next Paint helps developers understand how quickly a website responds when users click, tap, or interact with controls. A poor score can result from input delay, expensive event handlers, or slow rendering after the interaction.
Start by collecting real-user measurements and recording the slow interaction in browser developer tools. Then identify whether the main problem is JavaScript execution, rendering work, or another source of main-thread contention.
Breaking up long tasks, reducing unnecessary rendering, and providing prompt feedback can improve responsiveness. The most effective solution depends on the actual bottleneck, so measure the result after each meaningful change and verify that real users experience the improvement.
Join the conversation! Your thoughts help the community grow.