⚡ Web performance

INP: reducing interaction latency on the main thread

What Interaction to Next Paint measures, why clicks and taps feel slow, and how to break up long JavaScript tasks to hand control back to the user quickly.

Interaction to Next Paint replaced First Input Delay among the Core Web Vitals in March 2024. The difference is substantial: the old metric only looked at the delay of the first interaction, while INP observes every interaction during the visit (clicks, taps, key presses) and reports a value close to the worst one. An INP up to 200 milliseconds is considered good, above 500 is poor. Anyone with a highly interactive site, such as a configurator or a members' area, notices it immediately.

The three parts of an interaction

Each interaction measured by INP is made of three times:

  1. input delay: the time between the user's gesture and the start of the event handlers. It is long when the main thread is already busy with another task;
  2. processing time: how long the click, keydown, pointerup and similar handlers take to finish;
  3. presentation delay: the time the browser needs to recalculate styles and layout and paint the next frame.

The user perceives the sum: they tap a button and for a moment nothing happens. Each of the three parts has different causes and remedies.

The main enemy: long tasks

The browser runs JavaScript, style calculation, layout and painting on a single main thread. A task lasting more than 50 milliseconds is considered "long" because, while it runs, nothing else can happen. If the user taps the screen during a 300 millisecond task, that time becomes input delay.

Typical sources of long tasks are the start-up of heavy frameworks, third-party scripts all executing together, manipulation of lists with thousands of items, and event handlers that do too much work in one go.

Handing control back to the browser

The central technique is called yielding: splitting the work into chunks and, between one chunk and the next, letting the browser handle input and painting. A widespread, compatible approach uses a promise with setTimeout:

function yieldToMain() {
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }
  return new Promise(r => setTimeout(r, 0));
}

async function processRows(rows) {
  for (let i = 0; i < rows.length; i++) {
    updateRow(rows[i]);
    if (i % 50 === 0) await yieldToMain();
  }
}

scheduler.yield() is available in Chromium-based browsers and has the advantage of resuming the work with higher priority than a plain timeout; the fallback guarantees it works everywhere.

Feedback first, everything else later

A practical principle: in the event handler, do only what is needed to show the user that the action was received, then defer the rest. If a click on «Add to cart» has to update the counter, send an analytics event, save to localStorage and recalculate recommendations, only the first item is urgent.

button.addEventListener('click', async () => {
  counter.textContent = ++total;   // visible immediately
  await yieldToMain();
  sendAnalytics('added');
  saveCart();
  recalculateRecommendations();
});

The frame with the updated counter is painted before the secondary work starts, and INP measures exactly that frame.

Reducing presentation delay

Even with fast handlers, an interaction can feel slow if the browser has to recalculate a huge layout. The most common causes:

  • a DOM with tens of thousands of nodes, where even a small class change on body forces many selectors to be re-evaluated;
  • interleaved layout reads and writes (reading offsetHeight, changing a style, reading again), which force several recalculations in the same frame;
  • inserting large blocks of HTML with innerHTML in response to the click.

Keeping the DOM lean, grouping reads before writes and updating only the necessary portion are fixes whose effect shows immediately.

How to find slow interactions

In the lab, the DevTools Performance panel shows the interactions track with the duration of the three parts; it is worth enabling CPU throttling to simulate a mid-range phone, because on a powerful laptop almost everything looks fast. In the field, the web-vitals library with attribution tells you the element on which the worst interaction happened and the event type: by collecting this data you discover, for example, that the bad INP comes entirely from the filter dropdown on a single page.

Common mistakes

  • Optimizing only the initial load and forgetting that INP is measured across the whole visit.
  • Moving work into a long setTimeout, which postpones the problem without solving it if the task then remains monolithic.
  • Testing only on desktop: the difference in computing power between devices makes INP above all a mobile metric.

In short

  • INP measures the latency of all interactions and reports a value close to the worst; good up to 200 ms.
  • It is made of input delay, processing time and presentation delay.
  • Long tasks on the main thread are the main cause: break them up and yield to the browser.
  • Show visual feedback immediately and defer secondary work.
  • Measure on slow devices or with throttled CPU and collect real data with attribution.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also