metric

Long Task

also called Main-Thread Blocking Task, Long Animation Frame

Any single piece of work holding the browser's main thread for more than 50 ms, during which no input is handled and nothing is painted - the unit in which an unresponsive page is actually measured.

performancemain-threadinpschedulingjavascript

A page finishes loading, looks complete, and ignores the first three taps. Nothing has failed and no request is slow. One function is running, and because rendering and scripting share a single thread, the browser cannot handle the tap, recalculate style, or paint a frame until that function returns.

A long task is the measurable unit of that stall: a task occupying the main thread for more than 50 ms. The threshold is not arbitrary. At 60 Hz a frame is budgeted at about 16 ms, so 50 ms is roughly three frames — the point at which a human notices that the interface has stopped answering.

Why it matters

Tail responsiveness is made almost entirely of long tasks. Interaction to next paint — a Core Web Vital since 12 March 2024 — is the sum of input delay, handler processing and the wait for the next paint, and a long task that began before the user touched anything still lands in that total, because the input sits in a queue behind it.

This is why the metric correlates with complaints that a page "feels broken" while every server-side number is healthy. The work is on the user's device, on one thread, and the median developer machine executes it four to ten times faster than a mid-range Android phone, so the team cannot feel it.

Implementation patterns

  • Break work into chunks and yield between them. scheduler.yield() where available, otherwise await new Promise(r => setTimeout(r, 0)). The total CPU time is unchanged; what changes is that input is serviced between pieces. This is the cheapest fix with the largest effect and it changes no product code.
  • Yield only when input is pending, using navigator.scheduling.isInputPending(), so a batch that nobody is waiting on runs at full speed.
  • Move pure computation to a web worker. Parsing a large JSON payload, diffing a document, or filtering 100,000 rows does not need the DOM. The worker's work never blocks input at all.
  • Prioritise with scheduler.postTask() rather than a hand-rolled queue, so background work is genuinely background.
  • Shrink the work instead of scheduling it better: virtualise long lists, hydrate interactive regions only, and defer third-party scripts, which commonly contribute the largest single tasks on a page.
  • Measure with PerformanceObserver on longtask and long-animation-frame, attributing each entry to a script URL so the owner is identifiable rather than merely the symptom.

Industry example

The 50 ms definition comes from the Long Tasks API work in the W3C web performance group, and the newer Long Animation Frames API extends it to whole frames, including the style and layout cost a task causes rather than only the script. The practical consequence documented repeatedly in field data is that a page can pass every loading metric and fail responsiveness, which is precisely the gap that replacing First Input Delay with interaction to next paint in 2024 was intended to close.

Failure scenarios

  • One 400 ms handler holds every tap and scroll for 400 ms. Users tap again, producing duplicate submissions.
  • Hydration of a whole component tree runs as a few very long tasks, so the page is painted and deaf for seconds.
  • A third-party tag manager evaluates on load and contributes a 300 ms task nobody on the team owns.
  • A requestAnimationFrame loop doing layout work blocks every frame, so the page is permanently sluggish rather than briefly stalled.
  • Chunking without yielding — splitting a loop into ten functions called in sequence is still one task.
  • Measuring in the lab only, where a fast machine keeps every task under 50 ms and the field distribution is never seen.

Trade-offs

Choose Gains Pays
Yielding between chunks input is serviced within a frame or two total wall-clock time grows slightly
Web worker main thread untouched serialisation cost and no DOM access
Shipping less code removes the task entirely product scope or a rewrite

Yielding is cheap and sufficient for most cases. A worker is the right answer when the work is large and pure, and the wrong answer when it must touch the DOM, because the postMessage round trip then adds latency to every step.

When not to use it

Long-task counts are a poor primary goal. Optimise the interactions users actually perform, measured in the field, rather than driving a long-task count to zero: a 60 ms task during idle time costs nobody anything, while a 45 ms task inside a keystroke handler is under the threshold and still makes typing feel heavy. For a batch screen that nobody interacts with during load — a printable report, a kiosk display — blocking the thread for a second is the correct engineering choice.

Interview question

Q: Field interaction to next paint is 500 ms at p75 while your synthetic runs show no long tasks at all. Give me three explanations, say which you would test first, and tell me what you would change if the cause turns out to be a third-party script you do not control.

What a strong answer covers: device-class difference between the lab machine and the field population; synthetic runs not performing the interaction that is slow; and attribution — long tasks from scripts the synthetic test blocks or that only load for logged-in users. For a third party: move it off the critical path, set a budget with a named owner, and if it cannot meet the budget, remove it, because the measurement is of your page.

Quick check

Quiz: Why does a 380 ms click handler delay an animation that has nothing to do with it? Because rendering and scripting share one main thread, so no frame is produced until the handler returns.

Flashcard: At what duration does a task become a long task, and what cannot happen while it runs? — 50 ms; no input handling, no style or layout recalculation, no paint.