Field data shows a page's INP around 450 ms, but the click handler for the slow control measures about 8 ms of its own JavaScript. Where is the remaining latency likely to be, and how would you confirm it?
answer
- 8 ms clears one phase of three
- before the handler, or after it
- busy main thread vs heavy re-render
- split the phases in the field first
- throttle before reproducing locally
basics
~20 sA fast handler only rules out the processing phase. The remaining time is either input delay — the main thread was busy when the click arrived — or presentation delay, the style, layout and paint work the handler's DOM changes triggered. Capture the phase breakdown from real users to tell which.
solid answer
~50 sMeasuring 8 ms inside the handler only clears one of three phases. The other two are input delay and presentation delay, and either can hold 400 ms. Input delay means the main thread was mid-task when the user clicked — startup work, a timer, a third-party script — so nothing could be dispatched yet; it tends to cluster right after load and on slow devices. Presentation delay means the handler returned quickly but dirtied enough of the page that style recalculation, layout and paint could not fit into the next frame — a common signature when a click swaps in a large list or toggles a class high in the DOM. To confirm, I would collect the per-phase durations from real users via the Event Timing API rather than reason from a local click, since the field population is where the 450 ms lives, then reproduce on a throttled device profile once I know which phase dominates.
code
javascript · 15 linesconst observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.interactionId) continue;
navigator.sendBeacon('/rum/interactions', JSON.stringify({
name: entry.name,
target: entry.target ? entry.target.id : null,
inputDelay: entry.processingStart - entry.startTime,
processing: entry.processingEnd - entry.processingStart,
presentation: entry.startTime + entry.duration - entry.processingEnd,
total: entry.duration,
}));
}
});
observer.observe({ type: 'event', buffered: true, durationThreshold: 40 });go deeper
Remember that the handler is only the middle of three phases, so a fast handler does not explain a slow interaction on its own.
Name the two remaining suspects — a busy main thread before dispatch and heavy style/layout/paint afterwards — and show the arithmetic that pulls both out of an Event Timing entry.
Lead with diagnosis: collect the phase split and the interaction target from real users, segment by device class, and only then reproduce under throttling. Say plainly that optimising the 8 ms handler would move nothing.
Turn it into a durable capability — per-phase interaction telemetry with the target element attached, so responsiveness regressions are attributable to a team or a vendor instead of debated as a metric artefact.
## The trap in the question "My handler is fast" is the single most common wrong turn in INP work. It is a statement about **one third** of the interaction. INP runs from the input to the next paint, and the handler sits in the middle. Eight milliseconds of handler with 450 ms of total latency means roughly 440 ms is being spent either before the handler could start or after it returned. So the first move is not to optimise anything. It is to split the number. ## Hypothesis A — input delay The browser could not dispatch the event because the main thread was already running a task, and tasks are not interruptible. Everything the user does during that window queues. Signatures: - Latency is much worse in the first seconds of the page's life, while startup, hydration and tag managers are still executing. - Latency correlates with device class — the same task that takes 40 ms on a developer laptop takes 300 ms on a mid-range phone. - Bad interactions cluster at particular moments rather than being spread evenly, because they collide with periodic work such as an interval or a polling refresh. - The code you own is innocent: a third-party script you do not control is often the occupant. ## Hypothesis B — presentation delay The handler returned in 8 ms, but what it *asked for* was expensive. Setting a piece of state, swapping a class near the root, or appending a large subtree costs almost nothing inside the handler and a great deal afterwards, when the browser recalculates styles, runs layout, paints and composites before it can present a frame. Signatures: - Latency scales with the amount of content the interaction reveals — fine with ten rows, terrible with a thousand. - The interaction is a filter, sort, tab switch, accordion expand, or anything that replaces a large region. - The handler is short but the frame that follows it is long. - Work scheduled to run immediately after the handler — microtasks, animation-frame callbacks — extends the same frame. ## Confirming it, in the right order **1. Split the phases in the field.** The Event Timing API gives the timestamps for real interactions from real users: ```js new PerformanceObserver((list) => { for (const e of list.getEntries()) { if (!e.interactionId) continue; send({ target: e.target?.id, inputDelay: e.processingStart - e.startTime, processing: e.processingEnd - e.processingStart, presentation: e.startTime + e.duration - e.processingEnd, total: e.duration, }); } }).observe({ type: 'event', buffered: true, durationThreshold: 40 }); ``` Field-monitoring libraries expose the same breakdown for the interaction that determined the page's INP, which is usually less work than rolling your own. **2. Identify the element.** Knowing *which* control produced the worst interactions narrows the search far more than the number does. Report the interaction target alongside the phases. **3. Only then reproduce locally**, with CPU throttling that resembles the device population in your field data. Without throttling a mid-range phone's 300 ms task shows up as 40 ms and you conclude, wrongly, that there is no problem. **4. If input delay dominates**, find the occupying task. The Long Animation Frames API attributes long frames to the scripts responsible, which is what turns "something was blocking" into "this file was blocking". **5. If presentation delay dominates**, look at what the handler changed and how much of the page had to be re-rendered as a result. ## Why field first, lab second The 450 ms exists in a distribution of real users on real devices doing real things. A local click reproduces one point in that distribution — usually the fastest one. Starting in the lab risks concluding that the metric is wrong. Starting in the field tells you which phase, which control, and which device segment; the lab then reproduces a case you already understand. ## The fixes follow the phase - **Input delay** → free the main thread around interaction: defer or sandbox third parties, break long startup work, stop polling on a hot interval. - **Presentation delay** → shrink the rendering the interaction requests: render a page of results rather than all of them, keep the invalidated region small, and paint a cheap acknowledgement first so the measured interaction ends early. Notice that neither fix touches the handler. Optimising 8 ms down to 4 ms would have moved INP by less than one percent — which is precisely why the diagnosis has to come before the work.
- Why is reproducing this locally on your own machine a poor first step?Because a developer machine sits at the fast end of the distribution the 450 ms came from. Startup tasks and rendering work that take hundreds of milliseconds on a mid-range phone finish in tens on a laptop, so the interaction looks fine and you conclude the field data is wrong. Split the phases in the field first, then reproduce with CPU throttling.
- How would you tell input delay from presentation delay if you only had aggregate field numbers and no phase breakdown?Look at timing and shape. Input delay clusters in the seconds after load and correlates with device class, since it is competing main-thread work. Presentation delay scales with how much content the interaction reveals — bad on large data sets, fine on small ones. Adding the phase breakdown is still the right fix; this is only triage.
- The worst interactions come from a control rendered by a third-party widget. What changes about your approach?You lose the ability to shorten its handler or its rendering, so the lever becomes containment: load it later or only on demand, isolate it so its tasks do not block your controls, and set an expectation with whoever owns the vendor relationship. Document the cost in field terms — it is a negotiating position, not a code change.
- Why does painting a pressed state before the heavy work help this specific case?Because INP stops at the next paint after handling. If the handler applies a visible acknowledgement and defers the large update, the frame that answers the user arrives quickly and the measured interaction is short. It does not reduce total work, but it removes the rendering cost from the span the metric measures — and the user genuinely does feel the response sooner.
saying these in an interview costs you the question
- Concludes the metric is wrong because the handler is fast
- Optimises handler code without splitting the phases
- Reproduces on an unthrottled developer machine and finds nothing
- Assumes the remaining time is network latency
- Blames rendering without checking input delay first