What Is Total Blocking Time and How to Reduce It

Total Blocking Time (TBT) is a critical web performance metric that measures how long a webpage remains unresponsive to user input during loading. This article explains what TBT is, why long-running JavaScript execution degrades page responsiveness, and how breaking large JavaScript tasks into smaller chunks directly eliminates main-thread blocking to significantly improve your Core Web Vitals.

Understanding Total Blocking Time (TBT)

Total Blocking Time measures the total amount of time between First Contentful Paint (FCP) and Time to Interactive (TTI) where the browser’s main thread is blocked long enough to prevent input responsiveness.

The browser handles UI rendering, user interactions, and JavaScript execution on a single thread. When a task runs on the main thread for more than 50 milliseconds, it is classified as a Long Task. The time that exceeds the 50ms threshold is considered the “blocking time.”

For example: * A task taking 40ms has 0ms blocking time. * A task taking 90ms has 40ms blocking time (90ms - 50ms). * A task taking 200ms has 150ms blocking time (200ms - 50ms).

TBT is the sum of all blocking portions across all long tasks during the page load window.

How Long JavaScript Tasks Harm User Experience

When the main thread is occupied executing a heavy JavaScript script, it cannot process user interactions such as clicks, taps, or keyboard presses. If a user interacts with the page during a long task, the browser must wait until the task completes before it can respond, leading to noticeable UI lag, perceived unresponsiveness, or dropped frames.

How Breaking Up JavaScript Tasks Reduces TBT

Breaking up long JavaScript tasks directly targets the mathematical formula used to calculate TBT by eliminating task durations that exceed 50ms.

Consider an unoptimized script that performs heavy computation in a single continuous task lasting 250ms. * Single Task: 250ms total execution time = 200ms TBT (250ms - 50ms).

If you split that identical 250ms workload into five distinct 50ms sub-tasks: * Split Tasks: Five 50ms tasks = 0ms TBT (each task has 50ms - 50ms = 0ms blocking time).

Although the total execution time remains the same (250ms), the main thread yields back to the browser between each 50ms task. This allows the browser’s event loop to interleave and process pending user interactions immediately, eliminating UI freezing.

Practical Methods to Break Up JavaScript Tasks

1. Yielding to the Main Thread with scheduler.yield()

Modern browsers support the scheduler.yield() API, which pauses execution to let the browser handle user input and rendering updates before resuming the task:

async function processLargeData(items) {
  for (const item of items) {
    processItem(item);
    
    // Yield execution back to the browser periodically
    if ('scheduler' in window && 'yield' in scheduler) {
      await scheduler.yield();
    }
  }
}

2. Using setTimeout as a Fallback

In environments without scheduler.yield(), wrapping sub-tasks in a zero-delay setTimeout pushes remaining work to the end of the event loop queue, giving input events higher priority:

function yieldToMain() {
  return new Promise((resolve) => setTimeout(resolve, 0));
}

3. Offloading CPU-Heavy Work to Web Workers

Tasks that require heavy data processing, encryption, or complex calculations do not need to run on the main thread. Offloading these computations to a background Web Worker removes them from the main thread entirely, keeping TBT at zero.

4. Code Splitting and Lazy Loading

Reduce the initial volume of JavaScript by splitting code bundles. Only load and execute scripts required for the immediate view, and dynamically import secondary features only when requested by the user.