
Fix High INP: Long Tasks and Event-Handler Patterns
Practical techniques for reducing Interaction to Next Paint scores by breaking up long tasks, deferring non-critical work, and restructuring event handlers.
If your INP score is above 200 ms, your site feels sluggish to real users—even if everything else loads quickly. This article is a hands-on companion to the INP-first checklist and focuses on the three most common causes of high INP: long tasks, expensive event handlers, and main-thread congestion.
It is aimed at front-end developers who have already identified an INP problem using field data or lab tools and need concrete patterns to fix it. The web development hub covers the broader JavaScript ecosystem, and the web fundamentals path provides structured progression from basics to production performance.
What makes a task "long"
The browser defines a long task as any JavaScript execution that occupies the main thread for more than 50 ms. During that time, the browser cannot process user input, run animations, or paint updates. Long tasks are the single biggest contributor to high INP.
Common sources of long tasks in 2026 codebases:
- Hydration: frameworks re-attaching event listeners and re-rendering the component tree after SSR
- Third-party scripts: analytics, A/B testing, consent management, and chat widgets
- State management: cascading re-renders triggered by a single state change
- JSON parsing: large API responses parsed on the main thread
Diagnosing long tasks step by step
Step 1: Record a performance trace
Open Chrome DevTools, go to the Performance panel, and click Record. Interact with the page the way a real user would—click buttons, open menus, type in inputs. Stop recording after 10–15 seconds.
Step 2: Find the interactions track
The Interactions lane shows every user interaction with its total duration broken into input delay, processing time, and presentation delay. Red or orange bars indicate interactions that exceed the "good" threshold.
Step 3: Trace the call stack
Click on a slow interaction and examine the flame chart below it. Look for the widest bars—those are the longest-running functions. Note the file and line number.
Step 4: Categorise the cost
For each slow function, decide whether the work is:
- Essential and synchronous: it must complete before the UI updates (e.g., validating a form field)
- Essential but deferrable: it must happen but not in this event handler (e.g., sending an analytics event)
- Non-essential: it should not run during this interaction at all (e.g., prefetching unrelated resources)
Fixing long tasks: practical patterns
Yielding with scheduler.yield()
The scheduler.yield() API pauses execution, lets the browser process pending input, and then resumes your code. Unlike setTimeout, it preserves the task's priority so you do not lose your place in the queue.
Use it inside loops or sequential operations where each step takes more than a few milliseconds. The pattern is:
- Do a chunk of work
- Yield
- Resume the next chunk
This keeps individual tasks under 50 ms while still completing the full operation.
Breaking up event handlers
If a single click handler does multiple things—update state, animate a transition, send an analytics ping, prefetch the next page—split those responsibilities:
- Immediate: update the UI to acknowledge the interaction (toggle a class, show a spinner)
- Deferred: use
requestAnimationFramefor visual follow-up work - Background: use
requestIdleCallbackorqueueMicrotaskfor non-visual work
Debouncing expensive recalculations
For input fields that trigger search, filtering, or computation on every keystroke, debounce the expensive part while keeping the visual feedback instant. Show the typed characters immediately; run the search after 150–300 ms of inactivity.
Moving computation to Web Workers
For genuinely CPU-intensive work—image processing, complex sorting of large datasets, cryptographic operations—move the computation off the main thread entirely. Be aware of serialisation costs: transferring large objects via postMessage has its own overhead. Use Transferable objects where possible.
Event handler anti-patterns to avoid
Reading layout, then writing, then reading again. This forces synchronous layout recalculation (layout thrashing). Batch all reads first, then batch all writes.
Attaching listeners to document instead of the target. Document-level listeners fire for every interaction on the page. Attach handlers to the narrowest possible element.
Synchronous localStorage access inside handlers. localStorage.getItem and setItem are synchronous and can take several milliseconds on slow storage. Cache values in memory and write asynchronously.
Forgetting { passive: true } on scroll and touch listeners. Without the passive flag, the browser must wait for your handler to decide whether to call preventDefault() before it can scroll. This adds direct input delay.
Measuring improvement
After applying fixes, measure in both lab and field:
- Lab: re-record a Performance trace. Confirm no task in the interaction path exceeds 50 ms. Check that the Interactions track shows green.
- Field: deploy and wait for 7 days of CrUX data. Compare the p75 INP before and after. You need at least 28 days of data for stable CrUX results.
- Ongoing: add a performance test to your CI pipeline that runs Lighthouse or Web Vitals on key pages and fails the build if INP regresses past your threshold.
Trade-offs
scheduler.yield()is not yet available in all browsers. You may need a polyfill or fallback tosetTimeout.- Web Workers add architectural complexity. Do not use them for trivial computations where simple debouncing would suffice.
- Aggressive code splitting helps initial load but can make interactions slower if the user triggers a lazy import. Preload modules you know will be needed.
Further reading on EBooks-Space
- Web Development topic hub — reading lists for JavaScript, HTML, and CSS
- Web Fundamentals learning path — from first pages to production-grade performance
- Fast Sites for Readers guide — speed principles for content-focused sites
- INP-first Core Web Vitals checklist — the companion checklist for this article