Optimizing INP Interaction Latency
A "Add to cart" button took 680ms to respond. Users clicked it twice, got duplicate items, and support tickets spiked. DevTools showed the click handler synchronously recalculated cart totals across 400 DOM nodes, reflowed the sidebar, and fired three analytics events — all on the main thread before the button's loading state rendered. Yielding the heavy work and deferring analytics dropped INP to 95ms.
How INP is measured
INP captures the latency of all interactions (click, tap, key press) and reports the worst one:
User click → Event dispatch → Handler execution → Style/layout → Paint → INP
Every phase adds to the total. A 10ms handler with 300ms of layout and paint still produces poor INP.
Identifying long tasks
Open DevTools → Performance → record → interact → stop. Look for red triangles marking tasks over 50ms.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 50) {
console.warn(`Long task: ${entry.duration.toFixed(0)}ms`);
}
}
});
observer.observe({ type: 'longtask', buffered: true });
Optimizing event handlers
Show visual feedback first, then do heavy work:
button.addEventListener('click', async () => {
button.classList.add('loading');
await new Promise(r => requestAnimationFrame(r));
const result = await processInChunks(data);
updateUI(result);
button.classList.remove('loading');
});
Breaking up long tasks
async function processInChunks(items, chunkSize = 50) {
const results = [];
for (let i = 0; i < items.length; i += chunkSize) {
results.push(...items.slice(i, i + chunkSize).map(processItem));
await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
}
return results;
}
Deferring non-critical work
button.addEventListener('click', () => {
showConfirmation();
requestIdleCallback(() => {
analytics.track('button_click');
});
});
Analytics, logging, and non-visible state updates should never block interaction feedback.
Offloading to web workers
Move computation off the main thread for data processing, filtering, and sorting. Workers can't touch the DOM but handle the heavy lifting before the main thread applies results.
Third-party script impact
Chat widgets, A/B testing frameworks, and tag managers often register global event listeners that add latency to every interaction. Defer third-party loading and audit with DevTools Event Listener breakpoints.
INP optimization checklist
- Measure field INP with web-vitals or CrUX
- Profile interactions in DevTools Performance panel
- Show visual feedback immediately
- Break up long tasks — chunk processing, scheduler.yield()
- Defer non-critical work — analytics, logging
- Offload computation to web workers
- Audit third-party scripts
Input delay vs. processing vs. presentation
Chrome DevTools breaks INP into subparts: input delay (main thread busy), processing time (your handler), and presentation delay (style/layout/paint). Fix the largest subpart first — they're independent optimizations.
React 19 transitions
Wrap non-urgent state updates in startTransition to keep inputs responsive:
import { startTransition } from 'react';
onChange={(e) => {
setInputValue(e.target.value); // urgent
startTransition(() => filterResults(e.target.value)); // deferred
}}
Measuring success in production
Deploy changes behind feature flags when possible so you can compare metrics between control and treatment groups. Use Real User Monitoring to capture performance data from actual devices and network conditions — lab tools alone miss the long tail of user experiences. Set up alerts for regressions: a 10% LCP increase week-over-week warrants investigation before it hits CrUX.
Document your baseline metrics before making changes. Performance work without measurement is guesswork. Share results with the team — concrete numbers ("LCP improved 800ms on mobile") build support for continued investment in web performance and reliability.
Review changes quarterly. Browser updates, new API support, and traffic pattern shifts can obsolete previous optimizations or create new opportunities. What worked in 2024 may not be the best approach in 2026.
Additional production considerations
Teams often underestimate the maintenance cost of performance optimizations. Automate what you can: CI bundle budgets, Lighthouse CI on PRs, and RUM dashboards that alert on regressions. Manual audits don't scale past a handful of pages.
Security and performance intersect more than teams expect. Third-party scripts that hurt INP also expand your attack surface. Self-hosting fonts and critical assets reduces both latency and supply-chain risk. Review every external dependency quarterly — remove what you no longer need.
Accessibility and performance share goals: semantic HTML helps screen readers and gives the browser better rendering hints. Native elements like dialog, popover, and details reduce JavaScript while improving accessibility. Prefer platform features over custom implementations when they meet your requirements.
Mobile users dominate traffic for most sites. Test on real mid-tier Android hardware, not just desktop Chrome. Simulated throttling in DevTools approximates network conditions but not CPU constraints. A fix that helps desktop may be invisible on mobile if the bottleneck is JavaScript execution, not network.
Collaborate with backend teams on TTFB and API response times. Frontend optimizations can't fix a 2-second server response. Set SLAs for API endpoints that feed critical pages and measure them in the same RUM pipeline as Core Web Vitals.
Debugging checklist
When something doesn't work as documented, verify browser support with Can I use before assuming a polyfill bug. Check the Network tab for failed resource loads, incorrect MIME types, and missing CORS headers. Use the Console for CSP violations and Trusted Types errors that silently block operations.
Compare behavior in incognito mode to rule out extension interference. Test with cache disabled during development but validate with realistic caching in staging. Read the specification for edge cases the tutorial skipped — MDN examples cover happy paths, not every boundary condition.
If performance regresses after deployment, roll back first and investigate second. Keep a changelog of performance-related changes linked to metric dashboards. Future you will need to know why that preload tag exists before removing it during a refactor.
Resources
- web.dev: Optimize INP
- web.dev: INP metric definition
- Long Animation Frames API
- Scheduler API (MDN)
- web-vitals attribution
Frequently asked questions
What replaced First Input Delay as a Core Web Vital?
Interaction to Next Paint (INP) replaced FID in March 2024. FID only measured the delay before the first event handler ran. INP measures the full interaction latency — from input to the next painted frame — across all interactions on the page. It captures slow handlers, layout thrashing, and rendering delays that FID missed entirely.
What INP score should I target?
Under 200 milliseconds at the 75th percentile is good. Between 200ms and 500ms needs improvement. Above 500ms is poor. INP reports the worst interaction latency (or 98th percentile in some tools) excluding outliers. Focus on the interaction types with the highest latency — usually clicks on buttons that trigger heavy JavaScript.
How do I find which interactions cause poor INP?
Use Chrome DevTools Performance panel with Web Vitals enabled, or the Long Animation Frames API. Record a session, interact with the page, and look for long tasks over 50ms immediately after input events. The web-vitals library reports INP with attribution data showing which event type and target element caused the slow interaction.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →