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 valueAssessment
200 milliseconds or lessGood
More than 200 milliseconds and up to 500 millisecondsNeeds improvement
More than 500 millisecondsPoor

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:

JavaScript
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:

JavaScript
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:

  1. Open a data-heavy dashboard.
  2. Start recording in the Performance panel.
  3. Click the report filter.
  4. Wait for the interface to update.
  5. Stop recording.
  6. 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:

Bash
npm install web-vitals

Then register an INP callback:

JavaScript
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:

JavaScript
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:

InteractionObserved latency
Open navigation menu70 ms
Expand product details110 ms
Apply product filters420 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:

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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

ProblemLikely causePossible improvement
Buttons respond slowlyLong-running event handlersReduce synchronous work or split it into smaller tasks
Filters freeze the pageLarge calculations or excessive renderingOptimize filtering and render fewer elements
Menus open with a delayMain-thread contention or expensive layoutInspect event handlers and rendering activity
UI changes appear lateHeavy DOM updates or style recalculationReduce DOM work and simplify layout changes
Mobile INP is worse than desktopLimited CPU resources or heavier mobile workloadsTest on representative mobile devices and optimize the critical interaction path
INP varies between visitsDifferent interaction paths or unpredictable page workloadsCollect 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

AdvantagesLimitations
Reflects responsiveness across a page visitRequires enough field data for reliable conclusions
Helps identify interaction problems that page-load metrics missDoes not directly explain the cause of a delay
Supports mobile and desktop performance analysisResults depend on the interactions users perform
Encourages developers to investigate main-thread and rendering workLaboratory tests may not reproduce real-user conditions
Provides a standardized Core Web Vital targetImproving 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.