Featured image of post Approaches to Improving Interaction to Next Paint (INP) Featured image of post Approaches to Improving Interaction to Next Paint (INP)

Approaches to Improving Interaction to Next Paint (INP)

Understand the metrics behind Core Web Vitals' Interaction to Next Paint (INP) and learn how to prevent main-thread blockage in JavaScript.

Introduction

In Google’s Core Web Vitals metrics, which measure the user experience of web pages, FID (First Input Delay) has officially been replaced by INP (Interaction to Next Paint).

While FID only measured the response speed of the very first user interaction, INP evaluates the responsiveness of all clicks, taps, and keyboard inputs across the entire lifecycle of the page visit. It logs the delay between an interaction and the subsequent visual update (the “Next Paint”).

This article breaks down how INP is calculated and shares strategies to optimize JavaScript execution to prevent main thread blocking.


1. What is INP and How is it Measured?

INP measures the time it takes from when a user triggers a click, tap, or key press to when the browser successfully renders the updated frame (Next Paint) on the screen.

The INP score is composed of three distinct phases:

[User Interaction]
   ├─► 1. Input Delay
   │      The time spent waiting for other tasks on the main thread to finish.
   ├─► 2. Processing Time
   │      The time taken to execute the JavaScript event listeners (e.g., onClick).
   └─► 3. Presentation Delay
          The time the browser spends recalculating layout and painting the frame.
[Frame Updated (Next Paint)]

To provide a good user experience, websites should aim for an INP of 200 milliseconds or less.


2. Strategy 1: Breaking Up Long Tasks

The primary cause of poor INP is “Long Tasks”—JavaScript operations that occupy the main thread for 50ms or longer. When a long task runs, user inputs are blocked, leading to noticeable interface delay.

To avoid blocking the main thread, large tasks should be broken down into smaller chunks to let the browser yield control back to the layout engine.

Unoptimized Code (Blocking the Main Thread)

function processHeavyData(items) {
  // Heavy calculations on a large array block the main thread for seconds
  for (const item of items) {
    doHeavyCalculation(item);
  }
}

Optimized Code (Yielding to the Main Thread)

Using the modern scheduler.yield() API (or a fallback like setTimeout) allows the browser to process pending user inputs and paint frames during long-running tasks.

// Helper to yield control to the browser compositor
const yieldToMain = () => {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  return new Promise(resolve => setTimeout(resolve, 0));
};

async function processHeavyDataInChunks(items) {
  let count = 0;
  for (const item of items) {
    doHeavyCalculation(item);
    count++;

    // Yield control every 50 iterations to allow rendering
    if (count % 50 === 0) {
      await yieldToMain();
    }
  }
}

3. Strategy 2: Minimizing Event Handler Processing Time

In component-driven frameworks like React or Vue, triggering multiple state updates within a click handler can cause heavy virtual DOM reconciliations that extend the “Processing Time” phase.

Best Practices

  • Batch State Updates: Group updates together to minimize rendering cycles.
  • De-prioritize Async Fetching: Update the UI immediately to show a loading indicator, then run expensive data fetching or API mutations in the background.

4. Strategy 3: Reducing Presentation Delay

Even if your JavaScript completes quickly, your INP score will suffer if the browser takes too long to paint the updated layout.

Preventing Layout Thrashing

Layout thrashing occurs when JavaScript reads layout values (like offsetHeight) and immediately writes styles in a way that forces the browser to recalculate the page layout repeatedly.

  • Bad Practice:
    const width = element.offsetWidth; // Read
    element.style.width = (width + 10) + 'px'; // Write (forces immediate layout recalculation)
    
  • Best Practice: Batch read operations before write operations, or use requestAnimationFrame to schedule styling updates for the next rendering frame.

Using CSS content-visibility

For off-screen elements (like complex footers or below-the-fold grids), applying content-visibility: auto tells the browser to skip layout and rendering computations until the user scrolls them into view.

.heavy-footer-section {
  content-visibility: auto;
  contain-intrinsic-size: 500px; /* Reserves layout space to prevent scrollbar jumping */
}

Conclusion

Optimizing INP is essential for search engine visibility and reducing user drop-off on mobile devices.

  1. Identify and split long JavaScript tasks (>50ms) using yield or setTimeout.
  2. Minimize state changes in click handlers to show loading states instantly.
  3. Avoid layout thrashing and use CSS containment to reduce rendering cost.

Implementing these techniques will make your web application feel fast and responsive to user input.