awesome-everything RU
↑ Back to the climb

Browser & Frontend Runtime

Event loop: rescue an interaction from INP hell

Crux Hands-on project — drive a real interaction into a long task, diagnose it with LoAF, chunk it under the 50 ms bar, and prove the INP fix with before/after numbers and a CI gate.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 240 min

Reading about long tasks is not the same as pulling an interaction out of one. Build a page with a deliberately janky interaction, measure its INP under realistic throttling, diagnose the offending frame with LoAF, and apply the unit’s yield discipline until input responds within one frame — with evidence at every step.

Goal

Turn the unit’s timing model into a reproducible engineering loop: reproduce a long task, attribute it from telemetry, chunk it under the 50 ms bar with a real task-level yield, defend the win with a CI gate, and verify with before/after INP numbers.

Project
0 of 7
Objective

Take an interaction that blocks the main thread (your own app or the starter below) and bring its INP under 200 ms p75 on throttled hardware — without removing the feature — proving each step with measurements, not estimates.

Requirements
Acceptance criteria
  • A before/after table: INP p75, longest main-thread task (ms), and dropped-frame count during a fixed typing sequence — all measured under 4x CPU throttling, not estimated.
  • No single main-thread task exceeds 50 ms in the after trace; the LoAF stream no longer reports a slow frame for the interaction.
  • INP p75 holds under 200 ms for the interaction at sustained typing speed.
  • A one-paragraph write-up naming which yield primitive you used and why a microtask yield (await Promise.resolve()) would not have worked, referencing your trace.
Senior stretch
  • Add an INP CI gate: a Playwright test in headless Chrome with --cpu-throttling-rate=4 that replays the typing sequence, measures INP via the same PerformanceObserver code, and fails the build if p75 crosses the budget. Show it catches a regression you intentionally reintroduce.
  • Add a starvation guard: write the classic Promise.resolve().then(self) freeze, observe zero frames painted and INP over 1 s in a trace, then break the cycle with one task-level yield and show the page recovers.
  • Port the same heavy handler to a Node HTTP endpoint, measure event-loop lag with perf_hooks.monitorEventLoopDelay() under load, and show the Worker-thread fix drops p99 lag the same way the browser fix dropped INP.
  • Wire the LoAF sourceCharPosition through a real sourcemap (source-map library or a Sentry-style resolver) so telemetry reports the original file:line, not the bundle position.
Recap

This is the loop you will run in every real INP incident: reproduce the long task under throttling, attribute the slow frame with LoAF plus a sourcemap, chunk the work under the 50 ms bar with a genuine task-level yield (never a microtask), push CPU-bound work to a Worker when chunking is not enough, and verify with before/after INP numbers under identical load. Gate it in CI with the same throttling so the win cannot silently regress. Doing it once on a toy page makes the production version muscle memory.

Continue the climb ↑The render pipeline: six stages from bytes to pixels
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.