# Long tasks

> Source: <https://webperf-snippets.net/Interaction/LongTask>

Tracks tasks that block the main thread for more than 50ms. Long tasks prevent the browser from responding to user input, causing poor [Interaction to Next Paint (INP)](https://web.dev/articles/inp) and sluggish user experience.

### Why this matters

When the main thread is blocked by a long task, the browser cannot respond to clicks, taps, or keyboard input. This creates a frustrating experience where the page feels "frozen". Long tasks are the primary cause of poor INP scores and should be broken up into smaller chunks.

> **Note:** **Recommendation:** For better debugging, consider using [Long Animation Frames (LoAF)](https://webperf-snippets.net/Interaction/Long-Animation-Frames.md) which provides full script attribution and render timing.

### Long tasks vs long animation frames

| Aspect                  | Long Tasks     | LoAF                        |
| ----------------------- | -------------- | --------------------------- |
| Threshold               | > 50ms         | > 50ms                      |
| Script attribution      | Container only | Full (function, source URL) |
| Render time included    | No             | Yes                         |
| Forced layout detection | No             | Yes                         |
| Browser support         | Chrome 58+     | Chrome 123+                 |

### When to use long tasks

- Broader browser support needed
- Simple detection of blocking work
- Legacy monitoring systems

### Severity thresholds

| Severity    | Duration  | Impact              |
| ----------- | --------- | ------------------- |
| 🟢 Low      | 50-100ms  | Minor delay         |
| 🟡 Medium   | 100-150ms | Noticeable lag      |
| 🟠 High     | 150-250ms | Poor responsiveness |
| 🔴 Critical | > 250ms   | Severe blocking     |

### Snippet

```js
// Long Tasks Tracking
// https://webperf-snippets.net

(async () => {
  if (!PerformanceObserver.supportedEntryTypes?.includes("longtask")) {
    console.warn("⚠️ longtask entries are not supported in this browser.");
    return { script: "LongTask", status: "unsupported", error: "longtask entries not supported in this browser" };
  }
  const formatMs = (ms) => `${Math.round(ms)}ms`;

  // Severity helpers
  const getSeverity = (duration) => {
    if (duration > 250) return { level: "critical", icon: "🔴", color: "#ef4444" };
    if (duration > 150) return { level: "high", icon: "🟠", color: "#f97316" };
    if (duration > 100) return { level: "medium", icon: "🟡", color: "#eab308" };
    return { level: "low", icon: "🟢", color: "#22c55e" };
  };

  // Track all long tasks
  const allTasks = [];

  // Item returned for agents: the same shape at start and in the summary. Slowest first, at most 50.
  const toItems = (tasks) =>
    [...tasks]
      .sort((a, b) => b.duration - a.duration)
      .slice(0, 50)
      .map((t) => ({
        startTimeMs: Math.round(t.startTime),
        durationMs: Math.round(t.duration),
        blockingTimeMs: Math.round(t.blockingTime),
        severity: t.severity,
        attribution: t.attribution,
      }));
  let totalBlockingTime = 0;

  try {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        const duration = entry.duration;
        const blockingTime = Math.max(0, duration - 50); // Time over 50ms threshold
        totalBlockingTime += blockingTime;

        const sev = getSeverity(duration);

        const task = {
          startTime: entry.startTime,
          duration,
          blockingTime,
          severity: sev.level,
          attribution: entry.attribution?.[0]?.containerType || "unknown",
          containerId: entry.attribution?.[0]?.containerId || "",
          containerName: entry.attribution?.[0]?.containerName || "",
        };
        allTasks.push(task);

        // Log each long task
        console.groupCollapsed(
          `%c${sev.icon} Long Task: ${formatMs(duration)} (blocking: ${formatMs(blockingTime)})`,
          `font-weight: bold; color: ${sev.color};`
        );

        console.log(`%cTiming:`, "font-weight: bold;");
        console.log(`   Start: ${formatMs(entry.startTime)}`);
        console.log(`   Duration: ${formatMs(duration)}`);
        console.log(`   Blocking time: ${formatMs(blockingTime)}`);

        // Attribution (limited in Long Tasks API)
        if (entry.attribution && entry.attribution.length > 0) {
          console.log("");
          console.log(`%cAttribution:`, "font-weight: bold;");
          entry.attribution.forEach((attr) => {
            console.log(`   Container: ${attr.containerType || "window"}`);
            if (attr.containerName) console.log(`   Name: ${attr.containerName}`);
            if (attr.containerId) console.log(`   ID: ${attr.containerId}`);
            if (attr.containerSrc) console.log(`   Source: ${attr.containerSrc}`);
          });
        }

        // Running totals
        console.log("");
        console.log(`%cSession totals:`, "font-weight: bold;");
        console.log(`   Long tasks: ${allTasks.length}`);
        console.log(`   Total blocking time: ${formatMs(totalBlockingTime)}`);

        console.groupEnd();
      }
    });

    observer.observe({ type: "longtask", buffered: true });

    // Summary function
    window.getLongTaskSummary = () => {
      console.group("%c📊 Long Tasks Summary", "font-weight: bold; font-size: 14px;");

      if (allTasks.length === 0) {
        console.log("   No long tasks recorded.");
        console.log("   This is good! The main thread has been responsive.");
        console.groupEnd();
        return {
          script: "LongTask",
          status: "ok",
          count: 0,
          details: {
            totalBlockingTimeMs: 0,
            worstTaskMs: 0,
            avgDurationMs: 0,
            bySeverity: { critical: 0, high: 0, medium: 0, low: 0 },
          },
          items: [],
        };
      }

      const durations = allTasks.map((t) => t.duration);
      const worst = Math.max(...durations);
      const avg = durations.reduce((a, b) => a + b, 0) / durations.length;

      const worstSev = getSeverity(worst);

      // Statistics
      console.log("");
      console.log("%cStatistics:", "font-weight: bold;");
      console.log(`   Total long tasks: ${allTasks.length}`);
      console.log(`   Total blocking time: ${formatMs(totalBlockingTime)}`);
      console.log(
        `   Worst task: %c${formatMs(worst)}`,
        `color: ${worstSev.color}; font-weight: bold;`
      );
      console.log(`   Average duration: ${formatMs(avg)}`);

      // By severity
      const bySeverity = {
        critical: allTasks.filter((t) => t.severity === "critical").length,
        high: allTasks.filter((t) => t.severity === "high").length,
        medium: allTasks.filter((t) => t.severity === "medium").length,
        low: allTasks.filter((t) => t.severity === "low").length,
      };

      console.log("");
      console.log("%cBy severity:", "font-weight: bold;");
      console.log(`   🔴 Critical (>250ms): ${bySeverity.critical}`);
      console.log(`   🟠 High (150-250ms): ${bySeverity.high}`);
      console.log(`   🟡 Medium (100-150ms): ${bySeverity.medium}`);
      console.log(`   🟢 Low (50-100ms): ${bySeverity.low}`);

      // Timeline
      if (allTasks.length > 0) {
        console.log("");
        console.log("%c⏱️ Timeline:", "font-weight: bold;");

        const tableData = [...allTasks]
          .sort((a, b) => b.duration - a.duration)
          .slice(0, 10)
          .map((t) => {
            const sev = getSeverity(t.duration);
            return {
              "": sev.icon,
              Start: formatMs(t.startTime),
              Duration: formatMs(t.duration),
              Blocking: formatMs(t.blockingTime),
              Container: t.attribution,
            };
          });
        console.table(tableData);
      }

      // Recommendations
      if (totalBlockingTime > 300) {
        console.log("");
        console.log("%c💡 Recommendations:", "font-weight: bold; color: #3b82f6;");
        console.log("   • Break up long tasks using setTimeout or scheduler.yield()");
        console.log("   • Move heavy computation to Web Workers");
        console.log("   • Defer non-critical JavaScript");
        console.log("   • Use requestIdleCallback for low-priority work");
        console.log("");
        console.log("%c🔍 For better debugging:", "font-weight: bold;");
        console.log("   Use the Long Animation Frames snippet for full script attribution");
      }

      console.groupEnd();

      return {
        script: "LongTask",
        status: "ok",
        count: allTasks.length,
        details: {
          totalBlockingTimeMs: Math.round(totalBlockingTime),
          worstTaskMs: Math.round(worst),
          avgDurationMs: Math.round(avg),
          bySeverity,
        },
        items: toItems(allTasks),
      };
    };

    // Initial message
    console.log("%c⏱️ Long Tasks Tracking Active", "font-weight: bold; font-size: 14px;");
    console.log("   Tasks blocking the main thread for >50ms will be logged.");
    console.log(
      "   Call %cgetLongTaskSummary()%c for statistics.",
      "font-family: monospace; background: #f3f4f6; padding: 2px 4px;",
      ""
    );

    // Return for agent. Chrome only exposes longtask entries through a PerformanceObserver,
    // so wait for the buffered observer above to deliver them.
    await new Promise((resolve) => setTimeout(resolve, 100));
    const longtaskBuffered = allTasks;
    const totalBlockingSync = longtaskBuffered.reduce((sum, t) => sum + Math.max(0, t.duration - 50), 0);
    const worstTaskSync = longtaskBuffered.length > 0 ? Math.max(...longtaskBuffered.map((t) => t.duration)) : 0;
    const bySeveritySync = {
      critical: longtaskBuffered.filter((t) => t.duration > 250).length,
      high: longtaskBuffered.filter((t) => t.duration > 150 && t.duration <= 250).length,
      medium: longtaskBuffered.filter((t) => t.duration > 100 && t.duration <= 150).length,
      low: longtaskBuffered.filter((t) => t.duration >= 50 && t.duration <= 100).length,
    };
    return {
      script: "LongTask",
      status: "tracking",
      count: longtaskBuffered.length,
      details: {
        totalBlockingTimeMs: Math.round(totalBlockingSync),
        worstTaskMs: Math.round(worstTaskSync),
        bySeverity: bySeveritySync,
      },
      items: toItems(allTasks),
      message: "Tracking long tasks. Call getLongTaskSummary() for statistics.",
      getDataFn: "getLongTaskSummary",
    };

  } catch (e) {
    console.error("%c⚠️ Long Tasks API not supported", "font-weight: bold;");
    console.error("   Error:", e.message);
    return { script: "LongTask", status: "unsupported", error: e.message };
  }
})();
```

### Understanding the results

### Real-time output

Each long task logs:

- Duration and blocking time (time over 50ms)
- Severity indicator (🟢/🟡/🟠/🔴)
- Attribution (container type, if available)
- Running session totals

### Summary function

Call `getLongTaskSummary()` in the console to see:

| Section         | Description                               |
| --------------- | ----------------------------------------- |
| Statistics      | Total tasks, blocking time, worst/average |
| By severity     | Count at each severity level              |
| Timeline        | Top 10 longest tasks sorted by duration   |
| Recommendations | Optimization suggestions                  |

### What is blocking time?

Blocking time is the portion of a long task that exceeds 50ms:

```
Task duration: 150ms
Blocking time: 150 - 50 = 100ms
```

Total Blocking Time (TBT) is a key metric for understanding main thread responsiveness.

### Blocking time visualization

Diagram: A task splits at 50 ms: the first 50 ms are non-blocking and everything beyond is blocking time

### Breaking up long tasks

Once a long task is detected, the fix is to *yield to the main thread* so the browser can handle pending input between chunks of work. A common misconception is that `queueMicrotask` yields, it does not: microtasks run within the same task, so the main thread stays blocked. Only scheduling a new task (for example with `setTimeout`, or the newer `scheduler.yield()`) actually gives the browser a chance to respond.

Step through the event loop to compare the three approaches:

Interactive demo: [Interactive visualization of the event loop comparing no yield, queueMicrotask, and setTimeout, and how each affects whether the main thread stays blocked](https://webperf-snippets.net/demos/yield-pipeline.html). Switch between the three scenarios and step through each to see when the browser can process input.

### Limitations

The Long Tasks API provides limited information:

| Information                | Available |
| -------------------------- | --------- |
| Task duration              | ✅ Yes     |
| Start time                 | ✅ Yes     |
| Container (iframe, window) | ✅ Yes     |
| Script URL                 | ❌ No      |
| Function name              | ❌ No      |
| Forced layout time         | ❌ No      |

For full script attribution, use [Long Animation Frames](https://webperf-snippets.net/Interaction/Long-Animation-Frames.md).

### Browser support

| Feature                                                                                           | Chrome | Edge | Firefox | Safari |
| ------------------------------------------------------------------------------------------------- | :----: | :--: | :-----: | :----: |
| [PerformanceLongTaskTiming](https://developer.mozilla.org/docs/Web/API/PerformanceLongTaskTiming) |  ✅ 58  | ✅ 79 |    ❌    |    ❌   |
| [PerformanceObserver](https://developer.mozilla.org/docs/Web/API/PerformanceObserver)             |  ✅ 52  | ✅ 79 |   ✅ 57  |  ✅ 11  |
| All of the above                                                                                  |  ✅ 58  | ✅ 79 |    ❌    |    ❌   |

Source: [MDN browser compatibility data](https://github.com/mdn/browser-compat-data), version 8.1.4.

### Further reading

- [Long Tasks API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceLongTaskTiming) | MDN
- [Are long tasks the main thread is doing?](https://web.dev/articles/optimize-long-tasks) | web.dev
- [Total Blocking Time (TBT)](https://web.dev/articles/tbt) | web.dev
