Metricum Lab
Experiment journalWeb performance

How to Fix INP: Trace the Slow Interaction Before Changing Code

Yurii PekachPublished Updated 15 min read

The page has loaded, but a button still responds late. Cutting a bundle at random or showing a spinner earlier does not establish the cause—or prove that the action finishes correctly. Start with one interaction: match it to a trace, locate the delay before processing, inside the callback or before presentation, and change one mechanism. Then verify both responsiveness and the useful result. This guide is for developers and technical SEOs who can record a browser journey and test a changed build. The included lab compares blocking work, yielding and a timer fallback without accessing production data.

Responsiveness and completion are two separate validation results.

One interaction → trace → change → verification

HTML · JS · timer fallback · validation worksheetDownload the lab

Investigation 01

Turn the INP warning into a reproducible interaction

A slow interaction is a better starting point than a bundle-size target. Interaction to Next Paint (INP) measures responsiveness in Core Web Vitals: click, tap and keyboard input to the next paint. Field assessment uses the 75th percentile, separately for mobile and desktop: good at 200 ms or less, needs improvement above 200 through 500 ms, poor above 500 ms. These boundaries classify the outcome; they do not identify the responsible function.

Sources:[2] web.dev

Choose a journey with a concrete failure: opening navigation while the page initializes, changing a catalog filter after scrolling, or submitting a form with validation errors. Write down the route, initial state, input, expected visible response and whether the problem happens during loading or later. A trace of an idle page cannot test a complaint about a filter used several minutes into a visit.

Where real-user attribution is available, use it to choose the journey. The attribution build of web-vitals exposes an interaction target and the input, processing and presentation parts of the reported interaction. Inspect the library version and collection policy before adding instrumentation. Prefer an allowlisted component name to raw text or a selector containing user data; never attach form values to performance telemetry.

Sources:[5] web.dev

Record the action, not just the load

  1. Establish the starting state

    Open Chrome DevTools → Performance. Set the viewport and throttling, note the browser version, and reproduce the application state before recording. Keep those conditions for the comparison.

    Expected result: The baseline describes a repeatable journey, not an unexplained score.

  2. Capture one suspect action

    Start Record, perform the action, wait for its visible result, and stop the recording. Find that action in the Interactions track; hover it and inspect its Summary.

    Expected result: The recording contains the intended action and shows its input, processing and presentation intervals.

  3. Inspect the matching interval

    Zoom into that interaction and inspect the Main track around it. Save the trace and name the event handler, preceding task or rendering work that you intend to test.

    Expected result: The suspected work overlaps the delay you are trying to explain. A long task elsewhere is not accepted as the cause.

Sources:[4] Chrome for Developers

Sources:[10] Chrome for Developers

For the distinction between URL-level field data, origin data and synthetic testing, use the Core Web Vitals testing guide. Here the task is narrower: identify the slow interaction, change one cause, and verify that the action still completes correctly.

Investigation 02

Locate the delay before choosing a fix

Read the interaction from left to right. Input delay is the wait before event callbacks start. Processing duration covers the callbacks. Presentation delay is the interval after processing until the next frame is presented. A fix aimed at the wrong interval can remove code without changing the delay that the user experiences.

Sources:[3] web.dev

One interaction, three different places to investigate

The phases are shown in execution order, not to scale. The example contains no measured timings.

  1. WAITInput delay

    The input has arrived. Its callbacks have not started.

  2. RUNProcessing duration

    Event callbacks are running.

  3. PAINTPresentation delay

    Callbacks have ended. The next frame is not yet visible.

Explanatory model based on web.dev: investigate the interval that dominates the recorded interaction.

Sources:[3] web.dev

Use the trace to choose a falsifiable test
Dominant intervalEvidence to inspectTest one explanation
Input delayA task occupies Main immediately before the event callbacks.Temporarily defer that task in a controlled build. Repeat the same early interaction.
Processing durationThe selected event callback performs expensive work.Replace or chunk that work without changing the input and required output.
Presentation delayThe callback has ended, but rendering work delays the frame.Reduce the affected DOM/style work and check that the same content is still usable.

Sources:[3] web.dev[4] Chrome for Developers

Do not classify work by its name alone. A geometry read after style invalidation can force layout inside a JavaScript callback, so rendering-related cost can appear during processing. Inspect the call stack and the forced-reflow evidence before assigning every layout problem to presentation delay.

Sources:[3] web.dev[9] Chrome for Developers

Keep the hypothesis small enough to reject. “The application has too much JavaScript” is not a test. “This filter sorts the full dataset synchronously before acknowledging the selection” is: hold the dataset and selection constant, change the sort scheduling, and inspect both the recorded delay and the final order. A shorter interaction with incorrect results is a failed change.

Investigation 03

Yield without losing control of the operation

When a callback owns a large piece of synchronous work, splitting it into smaller functions does not create a scheduling opportunity. The browser still executes them in the same task. An already-resolved promise is not a substitute: its continuation is a microtask, and microtasks are drained before the browser may update rendering.

Sources:[1] MDN Web Docs[7] web.dev

The laboratory kit uses scheduler.yield() when available and a timer when it is not. Feature detection is part of the implementation, not an optional compatibility note: MDN still marks this API as having limited availability. Both paths resume in a later task, but their scheduling priorities are not identical.

Sources:[6] MDN Web Docs[11] Chrome for Developers

The scheduling helper used by the lab

javascript · VERIFIED
function yieldToBrowser(forceTimer = false) {
  if (!forceTimer && typeof globalThis.scheduler?.yield === 'function') {
    return globalThis.scheduler.yield();
  }
  return new Promise(resolve => setTimeout(resolve, 0));
}

VERIFIED as part of lab.js in Chromium 144.0.7559.96 using a local DOM harness. Call with await inside an async operation. The timer path was also executed. This check establishes execution and output parity, not a measured INP improvement on a production site.

Sources:[6] MDN Web Docs[11] Chrome for Developers

In the complete example, a run validates the workload, marks the operation busy, and prevents a second run from overwriting its state. The yielding mode reports progress between chunks. A cooperative cancellation token discards a partial result; the final checksum is published only when all work has completed. Cleanup restores the controls even when the operation fails. These constraints matter more than making the helper one line shorter.

A yield also does not promise that every pending timer will run before the continuation. The native API prioritizes continuations differently from ordinary timer tasks. Test the interaction that must remain responsive—such as Cancel or a subsequent filter change—instead of treating the number of yield calls as a performance result.

Sources:[11] Chrome for Developers

For a real component, define who owns cancellation when the route changes, which state can be read between chunks, and how a newer request supersedes an older one. Yielding introduces points where other work can intervene. Do not ship a scheduling change until those intermediate states are intentional and stale results cannot replace the current view.

Investigation 04

Run the experiment with a result you can verify

Download the interaction laboratory. It contains plain HTML, CSS and JavaScript, a measurement worksheet, and operating instructions. There are no external scripts or telemetry. Open index.html in a browser from the extracted folder; where local-file policies prevent this, serve that folder with your existing local development server. The bundled code was executed in a DOM harness, not through a production HTTP route.

Compare three implementations of the same work

  1. Fix the workload

    Enter one work-item count and keep it unchanged across Blocking, Yielding and Timer fallback. Start small, then increase it until your machine shows a useful trace.

    Expected result: All completed modes process the same number of items and produce the same checksum.

  2. Record each run

    In DevTools Performance, record the corresponding Run action through completion. Compare the selected interaction, subsequent main-thread work and the final result.

    Expected result: You can distinguish earlier acknowledgement from earlier completion; a smaller elapsed counter is not used as an INP value.

  3. Try to interrupt the operation

    During a sufficiently long yielding run, use Ping and then Cancel. Repeat with keyboard activation. Start another run after cancellation.

    Expected result: Ping can update, cancellation discards the partial checksum, and controls return to a usable state. A frozen Cancel path is a failed usability check.

  4. Exercise the fallback

    Use Timer fallback even in a browser that supports scheduler.yield(). Repeat output and cancellation checks.

    Expected result: Correctness does not depend on the native scheduling API being present.

A quick acknowledgement is not a finished operation

Follow both the first response and the later useful result. These lanes describe responsibilities, not measured duration.

  1. RESPONSEAcknowledge the input

    Show the accepted action or a truthful busy state. Inspect the selected interaction.

  2. IN PROGRESSContinue the required work

    Keep later interactions usable. Respect cancellation and protect state.

  3. COMPLETEDeliver the usable result

    Check the output, final UI, focus and error path. Record completion separately.

INP ends at the next paint; completion and correctness require their own checks.

Sources:[2] web.dev

In the supplied functional check, all three modes completed 10,000 items with checksum 2364371375. The native yielding path and forced timer fallback both ran in Chromium 144.0.7559.96. A scheduled high-priority test action exercised Ping and cancellation, and an invalid workload was rejected. This is a synthetic correctness check, not a field sample, an Event Timing measurement or evidence that one mode is faster on your application.

Investigation 05

Change the work when scheduling is not enough

Yielding is not a reason to keep unnecessary work. If the trace shows repeated full-data processing for each input, first decide whether the same result can be obtained with less computation. If one indivisible calculation dominates, a worker may be more appropriate than increasingly small chunks around it. Workers cannot directly manipulate the DOM; message transfer and the eventual main-thread update remain part of the design.

Sources:[8] MDN Web Docs

Choose a change that matches the evidence
Observed mechanismCandidate changeWhat must still pass
Repeated CPU work with the same inputRemove redundant processing or cache with explicit invalidation.Changed inputs invalidate stale results; memory use stays acceptable.
One expensive non-DOM calculationMove the computation to a worker and define message ownership.Startup, transfer, error handling and final UI update do not erase the benefit.
Alternating style writes and geometry readsGroup reads before writes where dependencies allow.Layout and responsive behavior remain correct for changed content.
A large view is rebuilt after a small inputUpdate the affected region instead of rebuilding unrelated content.Focus, keyboard access and content availability remain intact.
A startup task delays the first inputDefer genuinely nonessential startup work in a controlled build.The feature needed by that early interaction is still ready.

Sources:[3] web.dev[8] MDN Web Docs[9] Chrome for Developers

Treat these as candidate changes, not a checklist to apply all at once. A worker is a poor answer to a problem dominated by rendering. A spinner does not repair a lost form submission. Removing content to obtain a shorter trace changes the user task. Keep the expected result fixed while changing the mechanism that produces it.

Performance changes can also move instability elsewhere. When a deferred result expands a panel, verify its reserved space and the final focus position. Use the CLS debugging guide for unexpected movement, and the LCP guide when the first useful content is late. Those are separate investigations; a lower INP does not certify them.

Investigation 06

Close the ticket with responsiveness and completion evidence

I would not accept a screenshot of a lower number as the release criterion. Keep the baseline trace, the changed build, the exact journey and the expected output together. Start with paired repetitions under fixed conditions—for example, five baseline and five changed runs as a practical investigation batch, not a claim of statistical certainty. Record the spread as well as the typical result and investigate inconsistent runs rather than deleting them.

Use the worksheet as a release gate

  1. Repeat the same journey

    Keep the route, dataset, viewport, browser, CPU/network settings and relevant cache state consistent. Save traces for both builds.

    Expected result: The suspected interval changes in the intended direction without changing the task.

  2. Verify completion and recovery

    Check the final data, next interaction, focus, cancellation, error state and repeated activation. Define rollback before widening the release.

    Expected result: A usable result is delivered and the application can recover; early feedback alone does not pass.

  3. Check the production cohort

    After release, compare the same device and page cohort in the available real-user data and annotate the deployment. Keep completion/error signals alongside responsiveness.

    Expected result: A lab improvement is not called a field improvement until the relevant field evidence supports it. A mixed cohort remains qualified.

Do not average route percentiles to manufacture a site-wide percentile, and do not call the longest of a few manual clicks the production INP distribution. Retain the measurement scope in the ticket: one reproduced interaction, a real-user page cohort, or an origin-level report. The comparison is only as useful as the consistency of that scope.

Start the next investigation by saving one trace of the action that fails, then name the interval you can change. Reduce or reschedule only the work supported by that evidence. The fix is complete when the response is usable, the operation remains correct, and the relevant post-release measurements confirm the change—not when a spinner appears sooner.

Sources and documentation

Verification dates are listed with the sources. Code examples state the scope of their testing.

  1. Using microtasks in JavaScript with queueMicrotask()MDN Web Docs · Checked September 29, 2026
  2. Interaction to Next Paint (INP)web.dev · Checked September 29, 2026
  3. Optimize Interaction to Next Paintweb.dev · Checked September 29, 2026
  4. Performance features referenceChrome for Developers · Checked September 29, 2026
  5. Find slow interactions in the fieldweb.dev · Checked September 29, 2026
  6. Scheduler: yield() methodMDN Web Docs · Checked September 29, 2026
  7. Optimize long tasksweb.dev · Checked September 29, 2026
  8. Using Web WorkersMDN Web Docs · Checked September 29, 2026
  9. Forced reflowChrome for Developers · Checked September 29, 2026
  10. CrUX methodologyChrome for Developers · Checked September 29, 2026
  11. Use scheduler.yield() to break up long tasksChrome for Developers · Checked September 29, 2026

Need to investigate a slow interaction?

Start with the route, journey and evidence of the delay. The deliverable is a focused change with responsiveness and correctness checks.

Discuss web performance