TL;DR / The Long Tasks API reports JavaScript tasks that block the main thread for more than 50ms, providing visibility into the specific execution spans that cause input delay and visual jank.
How It Works
Main Thread Timeline
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐
│ Task A │ │ Task B │ │ Task C │ │ Task D │ │ Task E │
│ 12ms │ │ 80ms !!! │ │ 5ms │ │ 120ms !!! │ │ 30ms │
└──────────┘ └────────────┘ └──────────┘ └────────────┘ └──────────┘
──────────────────────────────────────────────────────────────────────
50ms threshold ^ Long Task ^ Long Task
PerformanceEntry reported PerformanceEntry reported
The browser's main thread processes work in tasks: discrete units of execution that include JavaScript evaluation, DOM manipulation, style calculation, layout, paint, and event handling. A task runs to completion before the next task begins. When a task takes longer than 50ms, it is classified as a long task -- the threshold at which users begin to perceive the interface as sluggish.
The 50ms budget comes from the RAIL performance model. To maintain 100ms responsiveness (the human perception threshold for "instant" feedback), the main thread must be able to respond to input within 50ms, leaving the other 50ms for the browser to process the input event and produce a visual update. Any task exceeding 50ms delays input handling by at least that much.
Register a PerformanceObserver with type: 'longtask' to receive entries for every task exceeding 50ms. Each PerformanceLongTaskTiming entry contains startTime (milliseconds since navigation start), duration (total task time), name (always "self" for same-origin tasks, "cross-origin-ancestor" or similar for iframe-originating tasks), and attribution -- an array of TaskAttributionTiming objects identifying the container (frame, iframe) and script responsible.
The attribution is intentionally coarse for security reasons. For same-origin tasks, attribution[0].containerType reports the frame type, containerName and containerId identify the element, and containerSrc reveals the URL. For cross-origin iframe tasks, attribution is limited to the frame identification -- you cannot see the specific script or function that ran. This prevents timing side-channel attacks that could leak cross-origin execution details.
Long tasks directly degrade Core Web Vitals. First Input Delay (FID) measures the time a user's first input event waits in the task queue while a long task occupies the main thread. Interaction to Next Paint (INP) captures the same delay for all interactions throughout the session. Total Blocking Time (TBT), a lab metric correlated with FID, sums the excess duration beyond 50ms for every long task between FCP and TTI. A 200ms task contributes 150ms of blocking time.
Common causes of long tasks include: large JavaScript bundle evaluation (parsing and compiling scripts), expensive DOM operations (inserting thousands of nodes, triggering synchronous layout/reflow), complex style recalculation (after class changes affecting many elements), synchronous localStorage reads, and large JSON parsing via JSON.parse().
Mitigation strategies center on breaking long tasks into smaller chunks. scheduler.yield() (or the polyfill using setTimeout(0) or MessageChannel) yields control back to the browser mid-task, allowing pending input events and rendering to execute before resuming. scheduler.postTask() provides priority-based scheduling: 'user-blocking' for input-related work, 'user-visible' for rendering updates, and 'background' for non-urgent computation.
requestIdleCallback schedules work during idle periods (after frame rendering, before the next frame), with a deadline indicating how much time remains. However, it has no minimum frequency guarantee -- under heavy load, idle callbacks may never fire.
The isInputPending() API (Chrome) enables cooperative yielding: check within a long-running loop whether user input is waiting, and yield only when it is. This avoids the overhead of yielding every 5ms when no input is actually pending, while still maintaining responsiveness.
For measuring long tasks in production, collect entries via PerformanceObserver and beacon them to your analytics endpoint. Aggregate by page type and user segment. Set alerting thresholds: pages with TBT exceeding 300ms (the "good" threshold) need investigation. Cross-reference long task timestamps with user interaction events to identify which specific long tasks actually blocked user input versus those occurring during idle periods.
Gotchas
- The 50ms threshold is not configurable -- you cannot observe 30ms tasks or 100ms tasks; the API always reports tasks exceeding 50ms; for finer granularity, use the Event Timing API or User Timing marks
- Long Tasks API has no Firefox support -- it remains Chrome/Edge only; use TBT from Lighthouse for cross-browser lab measurement
- Script evaluation is a single task -- a 500KB synchronous script block creates one monolithic task; code splitting and
async/deferattributes are the only mitigation setTimeout(fn, 0)does not truly yield -- browsers clamp nested setTimeout to 4ms minimum after 5 calls, and the task queue may already have higher-priority work; usescheduler.yield()orMessageChannelfor reliable yielding- Attribution does not identify the specific function -- you get the container/frame origin, not a stack trace; combine Long Tasks data with the Performance panel flame chart for root-cause analysis