How the Long Tasks API Monitors Main-Thread Stalls
This article explains how the Long Tasks API enables JavaScript
applications to detect and diagnose main-thread execution stalls. By
leveraging the browser’s performance timeline infrastructure, the API
automatically flags any uninterrupted task exceeding 50 milliseconds and
delivers detailed diagnostic records to the application asynchronously
via the PerformanceObserver interface.
The 50-Millisecond Threshold
JavaScript runs in a single-threaded execution environment within the browser’s event loop. When a script, layout calculation, or rendering process occupies the main thread for too long, user inputs, animations, and page interactions freeze.
The Long Tasks API defines a “long task” as any contiguous, synchronous execution block that takes 50 milliseconds or longer to complete. This boundary is based on the RAIL performance model, which recommends responding to user input within 100 milliseconds. Reserving a 50-millisecond window allows the browser up to 50 milliseconds to finish ongoing tasks and another 50 milliseconds to process the incoming user event without perceivable latency.
The PerformanceObserver Notification Mechanism
Because a stalled main thread cannot execute monitoring code during the freeze itself, the Long Tasks API relies on passive, asynchronous reporting. It does not interrupt or poll the running thread. Instead, the browser’s internal engine monitors task execution times and logs entries to the Performance Timeline.
Developers access these notifications by registering a
PerformanceObserver configured to watch for the
longtask entry type.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn(`Long task detected: ${entry.duration}ms`);
console.log(`Started at: ${entry.startTime}ms`);
console.log('Attribution:', entry.attribution);
}
});
observer.observe({ type: 'longtask', buffered: true });The buffered: true flag ensures that any long tasks that
occurred prior to observer initialization (such as during initial page
load and script parsing) are retrieved and delivered to the
callback.
Asynchronous Delivery via the Event Loop
When a long task executes, the following sequence occurs:
- Detection: The browser engine measures the duration
of a discrete task in the event loop. If the task duration reaches or
exceeds 50 milliseconds, the browser creates a
PerformanceLongTaskTimingentry. - Buffering: The entry is stored in the browser’s internal performance timeline buffer.
- Queueing the Callback: Once the main thread becomes
idle, the browser queues a microtask or task to execute the registered
PerformanceObservercallback. - Data Delivery: The observer callback receives a list of entries, passing the execution details directly to the application without blocking ongoing interactions.
Structure of a Long Task Notification
Each reported PerformanceLongTaskTiming object contains
critical diagnostic metrics:
duration: The total elapsed time of the task in milliseconds (always \(\ge 50\text{ ms}\)).startTime: High-resolution timestamp indicating when the task started relative toperformance.timeOrigin.name: Describes the general context, such asself,same-origin,cross-origin-ancestor, orcross-origin-descendant.attribution: An array ofTaskAttributionTimingobjects providing deeper insights into the source of the stall, including the container type (e.g., aniframe), container ID, name, and script source URL if available.
Application Use Cases
JavaScript applications use the Long Tasks API to power Real User Monitoring (RUM) pipelines. By aggregating long task durations, teams can calculate Total Blocking Time (TBT), identify expensive third-party scripts, and isolate components causing input delay to optimize Core Web Vitals such as Interaction to Next Paint (INP).