Skip to content

Reducing INP

Reducing INP Through Code Improvements

Interaction to Next Paint (INP) is a critical metric for user experience, measuring the time between a user's interaction (e.g., a click or tap) and the browser's next paint. To minimize INP, focus on reducing JavaScript execution time, deferring non-critical scripts, and optimizing event handlers to ensure the main thread remains responsive.


1. Reduce JavaScript Execution Time

JavaScript execution on the main thread directly impacts INP. Prioritize optimizations to keep the main thread free for rendering and user interactions.

Code Splitting and Lazy Loading

Split large JavaScript bundles into smaller chunks and load them on demand. For example, in React applications:

// Using React.lazy and Suspense
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
  return (
    <React.Suspense fallback="Loading...">
      <LazyComponent />
    </React.Suspense>
  );
}
This ensures only critical code is loaded initially, reducing the time needed for the main thread to process scripts.

Web Workers

Offload CPU-intensive tasks (e.g., data processing, image manipulation) to Web Workers, which run in parallel with the main thread:

// Main thread
const worker = new Worker('worker.js');

worker.postMessage({ data: largeDataset });

// Worker thread (worker.js)
self.onmessage = (event) => {
  const result = processData(event.data);
  self.postMessage(result);
};

Avoid Long-Running Tasks

Use requestIdleCallback for non-urgent tasks or split long tasks into smaller chunks:

function processInChunks(data) {
  const chunkSize = 1000;
  for (let i = 0; i < data.length; i += chunkSize) {
    requestIdleCallback(() => {
      processChunk(data.slice(i, i + chunkSize));
    });
  }
}


2. Defer Non-Critical Scripts

Non-critical scripts can block the main thread and delay the first paint. Use asynchronous loading techniques to defer execution.

Async and Defer Attributes

Use async or defer for external scripts to avoid blocking rendering:

<!-- Async: loads and executes as soon as possible -->
<script src="script.js" async></script>

<!-- Defer: loads after parsing HTML, executes in order -->
<script src="script.js" defer></script>

Dynamic Imports

Load scripts dynamically using import() to defer execution until needed:

// Load a script on user interaction
document.getElementById('loadBtn').addEventListener('click', () => {
  import('./nonCriticalScript.js').then(module => {
    module.init();
  });
});

Move Scripts to the Bottom

Place non-critical scripts at the end of the HTML body to allow the DOM to load first:

<body>
  <!-- DOM content -->
  <script src="nonCriticalScript.js"></script>
</body>


3. Optimize Event Handlers

Event handlers can introduce latency if they perform heavy computations or trigger unnecessary re-renders. Streamline their execution.

Event Delegation

Use a single event handler for multiple elements to reduce overhead:

document.getElementById('parent').addEventListener('click', (e) => {
  if (e.target.matches('.child')) {
    handleChildClick(e.target);
  }
});

Throttle/Debounce Frequent Events

Limit the frequency of handlers for events like resize or scroll:

function debounce(func, wait) {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

window.addEventListener('resize', debounce(updateLayout, 200));

Avoid Unnecessary Re-renders

In React, use useCallback or useMemo to prevent redundant renders:

const handleClick = useCallback(() => {
  // Optimized logic
}, [dependency]);


Diagram: Main Thread Optimization

+-----------------------------+
|         Main Thread         |
|  +----------------+         |
|  |  JavaScript    |<-------+
|  |  Execution     |         |
|  +----------------+         |
|         |                 |
|         v                 |
|  +----------------+         |
|  |  Rendering     |<-------+
|  |  and Painting  |         |
|  +----------------+         |
+-----------------------------+

Key takeaways

  • Optimize the main thread by splitting JavaScript, using Web Workers, and avoiding long-running tasks.
  • Defer non-critical scripts with async, defer, or dynamic imports to prevent blocking.
  • Refine event handlers using delegation, throttling, and React optimizations to reduce latency.
  • Prioritize critical user interactions to ensure the main thread remains responsive during INP.