Web Performance

What Is Total Blocking Time (TBT) and How to Reduce It

Total Blocking Time is the lab proxy for interactivity. What TBT is, how it relates to INP, what causes a high score, and how to reduce it by taming JavaScript.

StackOptic Research Team16 May 20269 min read
Total Blocking Time — the TBT interactivity lab metric explained

A page can look finished and still be frozen. The content has painted, but tap a button and nothing happens — because the browser's main thread is still busy running JavaScript and cannot get to your click. Total Blocking Time (TBT) is the lab metric that captures exactly this: how long the page was unresponsive while it loaded. In short: TBT sums the blocking part of every long task — the time beyond 50ms — between First Contentful Paint and Time to Interactive, a good lab score is 200 milliseconds or less, and because it is driven by JavaScript it is the lab proxy for the field metric INP. This guide explains what TBT measures, how it relates to INP, what inflates it, and how to bring it down.

It is the lab-side companion to INP (Interaction to Next Paint) explained, the field metric it predicts.

What TBT measures

To understand TBT you need the idea of a long task. The browser handles work — running scripts, responding to input, painting — in tasks on the main thread, which can only do one thing at a time. A task that runs for 50 milliseconds or more is called a long task, because while it runs the thread is blocked: any tap, click or key press the user makes has to wait until the task finishes before the browser can respond.

TBT measures the total blocking time across page load by looking at every long task between First Contentful Paint (when content first appears) and Time to Interactive (when the page is reliably responsive). For each long task, it counts only the part beyond the 50ms threshold — the "blocking portion". So a task of 80ms contributes 30ms of blocking time; a task of 250ms contributes 200ms. TBT is the sum of all those blocking portions. A high number means the main thread was tied up for a long time during load, leaving the page unable to react.

Crucially, TBT is a lab metric. It is measured during a controlled Lighthouse load under simulated CPU and network throttling, which makes it reproducible and ideal for diagnosis — but it is not collected from real users.

The thresholds

Lighthouse rates TBT from its mobile lab test:

TBTRating
≤ 200msGood
200ms - 600msNeeds improvement
> 600msPoor

Because TBT is a lab measurement under throttling, the exact value shifts with the test configuration and the device profile. Treat it as a diagnostic and tracking number — run it the same way each time, watch it fall as you optimise — rather than as a literal figure your real users experience. For what real users experience, you look at INP.

TBT and INP: lab proxy and field metric

This is the relationship that makes TBT worth caring about. Interactivity has two measurements, and they serve different purposes.

TBTINP
TypeLab (synthetic)Field (real users)
MeasuresMain-thread blocking during loadResponsiveness of real interactions across the visit
SourceLighthouse / PageSpeed Insights labChrome UX Report
Core Web Vital?NoYes
Good threshold≤ 200ms (mobile lab)≤ 200ms (75th pct)
Used forDiagnosis, iteration, CI gatesRanking signal, real UX

They measure different things — TBT looks at blocking during load, INP looks at every interaction throughout the visit — but they share a common root cause: heavy JavaScript blocking the main thread. That is why they correlate strongly in practice: a page with high TBT almost always struggles with INP, and the work that fixes one fixes the other. The practical division of labour is to optimise against TBT in the lab because it is fast and reproducible, then confirm the win in INP field data because that is the Core Web Vital Google actually uses. For the field side in depth, see INP (Interaction to Next Paint) explained.

The causes of high TBT

Almost all blocking time comes from JavaScript, in a few recognisable forms.

CauseWhat is happeningPrimary fix
Large JS bundlesParsing, compiling and executing megabytes of script creates long tasksCode-split; remove unused JS; minify
Long tasksA single function does too much work in one uninterrupted runBreak work into chunks; yield to the main thread
Heavy hydrationA framework attaches interactivity to server-rendered HTML in one burstPartial/progressive hydration; islands; less JS
Third-party scriptsAnalytics, chat, tags and A/B tools run code you do not controlDefer; remove; consolidate via a manager

The unifying theme is that a few big tasks hurt far more than many small ones, because TBT only counts time beyond 50ms per task. Splitting one 300ms task (which contributes 250ms of blocking) into six 50ms tasks (which contribute zero blocking) can transform the metric without removing any actual work — you have simply given the main thread room to breathe between the pieces.

How to reduce TBT

Break up long tasks

The most direct fix is to split long tasks into smaller ones so the main thread can handle pending input between them. Where a function processes a large amount of work in one go, restructure it to yield periodically — modern approaches include explicitly yielding to the main thread (for example with scheduler.yield() where available, or by breaking work across setTimeout/requestIdleCallback boundaries) so the browser can respond to a tap before resuming. Defer work that does not need to happen immediately, and avoid doing expensive synchronous computation during the critical load window. Breaking tasks does not reduce total work, but it slashes blocking time, which is what TBT (and a user's sense of responsiveness) actually measures.

Ship less JavaScript

Less code means fewer and shorter tasks. Code-split so each page loads only the JavaScript it needs rather than the whole application; remove unused code and dead libraries; replace heavyweight utilities with native browser APIs; and minify what remains. This is the single most reliable way to lower TBT, because the parse-compile-execute cost scales with how much script the browser has to process. The full playbook is in how to reduce JavaScript and speed up your site, which is the natural companion to this metric.

Defer non-critical scripts

Scripts that are not needed for the initial view should not run during it. Add defer to non-critical scripts so they download without blocking the parser and run after the HTML is ready, and lazy-load components and scripts that are only needed on interaction or further down the page. Pushing non-essential work out of the load window directly reduces the long tasks that TBT counts.

Audit third-party scripts ruthlessly

Third-party tags — analytics, chat widgets, A/B testing, ad and marketing scripts, tag managers — are frequently the single biggest source of main-thread blocking, and they often load further scripts of their own. Inventory what is loaded, remove anything not earning its place, defer the rest so it runs after the critical content, and be wary of tag managers that let scripts proliferate unchecked. This audit alone often recovers more TBT than any amount of tuning your own bundle.

Tame hydration in single-page apps

Frameworks that server-render and then hydrate (attach interactivity to the server-rendered HTML in the browser) can create one large burst of main-thread work right at load — a classic TBT and INP problem. Look into partial or progressive hydration, islands architecture, and streaming, which hydrate less and later. The general rule holds across all of these: ship less JavaScript, hydrate less of the page, and do it later where you can.

How to measure and verify TBT

  1. Lighthouse / PageSpeed Insights. Both report TBT in the lab section and flag the culprits via "Reduce JavaScript execution time", "Minimize main-thread work", "Reduce the impact of third-party code" and "Reduce unused JavaScript", each with estimated savings.
  2. Chrome DevTools - Performance panel. Record a load; the main-thread track marks long tasks with red corner flags, and clicking each shows which script and function caused it. This is where you confirm a long task actually shrank after a change.
  3. DevTools - throttle the CPU. Set a 4x or 6x CPU slowdown in the Performance panel so the test approximates a mid-range phone — the execution cost of JavaScript is CPU-bound, so a task that is fine on your laptop can be a long task on real hardware.
  4. Coverage tab. Reload with the Coverage tab open to see what percentage of each script went unused, pointing straight at code-splitting and removal opportunities.

The diagnostic loop is: find the longest tasks, identify the scripts behind them, reduce or split that work, and re-record to confirm the long task is gone. For the broader testing context, see Core Web Vitals explained.

A worked example

Suppose Lighthouse reports a TBT of 720ms — "poor" — on mobile. The Performance panel shows two long tasks dominate: a 400ms task parsing and executing a large vendor bundle at load, and a 280ms task from a third-party tag manager that pulls in several marketing scripts. Two changes follow. First, you code-split the vendor bundle so the heavy charting library it contained loads only on the one page that uses it, removing most of that 400ms task from every other page's load. Second, you defer the tag manager and prune two scripts it was loading that no one could justify. Re-running Lighthouse, TBT falls to around 150ms, and a week later the field INP for those pages has moved from "needs improvement" into "good". This is the typical shape of TBT work — a small number of large, identifiable tasks rather than a thousand tiny ones.

Why TBT matters even though it is not a Core Web Vital

It is fair to ask why you should track a lab metric that Google does not use for ranking. The answer is controllability. INP is the Core Web Vital, but it is a field metric — you cannot run it on demand, it needs real-user data to accumulate, and it reflects interactions you cannot fully script. TBT, being a lab metric, can be run on every commit, gated in continuous integration, and used to catch a regression before it ships and shows up weeks later in INP field data. Because the two share a root cause, TBT is the early-warning system for INP: keep TBT good in the lab and you are very likely to keep INP good in the field. It is the metric you act on; INP is the metric you are ultimately judged by.

Common mistakes

  • Treating TBT as a real-user number rather than a lab proxy for INP.
  • Optimising bundle size while ignoring third parties that dominate the main thread.
  • Leaving long synchronous tasks intact when splitting them would erase the blocking time.
  • Testing without CPU throttling, so long tasks that hurt real phones stay hidden.
  • Shipping the whole app upfront instead of code-splitting per route.

Go deeper

Want your interactivity measured alongside the rest of your Core Web Vitals, SEO and security? Analyse any URL with StackOptic — free, no sign-up.

Frequently asked questions

What is Total Blocking Time (TBT)?

TBT is a lab performance metric that measures how long the main thread was blocked from responding to user input during page load. It sums the blocking portion of every 'long task' — any task running longer than 50 milliseconds — between First Contentful Paint and Time to Interactive, counting only the time beyond that 50ms threshold. A high TBT means the page spent a long time unable to react to taps and clicks while it was busy running scripts.

What is a good Total Blocking Time?

In Lighthouse's mobile lab test, a good TBT is 200 milliseconds or less. Between 200 and 600 milliseconds needs improvement, and above 600 milliseconds is poor. Because TBT is a lab metric measured under simulated throttling, the exact value depends on the test configuration, so use it for diagnosis and tracking improvement rather than as a real-user number. The field equivalent of interactivity is INP.

What is the difference between TBT and INP?

TBT is a lab metric that measures main-thread blocking during page load, while INP (Interaction to Next Paint) is a field metric that measures the responsiveness of real user interactions across the whole visit. TBT is a controllable proxy you optimise in the lab; INP is what Google measures from real users and uses as a Core Web Vital. They are related because the same heavy JavaScript that inflates TBT also tends to worsen INP, so reducing one usually helps the other.

What causes high Total Blocking Time?

Heavy JavaScript is the dominant cause. Large bundles take a long time to parse, compile and execute, creating long tasks that block the main thread. Expensive framework hydration in single-page apps, third-party scripts such as analytics and tag managers, and inefficient code that does too much work in one go all add long tasks. Because TBT counts only time beyond 50ms per task, a few big tasks hurt far more than many small ones.

How do I reduce Total Blocking Time?

Break long tasks into smaller chunks so the main thread can respond between them, using techniques like yielding to the main thread. Code-split so each page loads only the JavaScript it needs, defer non-critical scripts, and remove unused code. Audit third-party scripts ruthlessly, since they are often the biggest blockers. Then verify in the Chrome DevTools Performance panel, which flags long tasks and shows which scripts caused them.

Analyse any website with StackOptic

Get the full technology stack, performance, security and SEO report in seconds — free.

Analyse a website

Related articles