completefrontend
Open lab My progress
THE WEB PERFORMANCE FIELD GUIDE

Long tasks

Why a task of 50 ms or more makes a tap wait, how Total Blocking Time adds up those waits, how DevTools marks long tasks, and what long frames add.

By the end, you can
  • 01
    Explain why long tasks hurt tapsWork out how long a tap waits when it lands in a task, and why the line is drawn at 50 ms.
  • 02
    Read Total Blocking TimeAdd up the part of each long task past 50 ms between FCP and TTI, the way Lighthouse does.
  • 03
    Find long tasks, and what they missSpot them in DevTools, and tell when a long animation frame shows a slow screen that no long task does.

Why did Asha's tap wait?

Asha opens the Leaf and Kettle tea list on her phone. The list appears. She taps Add on Mint tea. Nothing happens. More than a third of a second later, the cart says 1.

Lesson 26.3 found the reason. A script called reviews.js was running when she tapped. It reads this week's reviews as the page starts. Ravi builds the site, and he can't drop it. The shop needs its popular teas. But he can change how the script runs.

So his question is this. Does it matter if the same work runs in one piece or in many? And how can he tell, without Asha there to tap?

Start with Asha's tap. Here are three times from the course's recording of it:

Asha's tap, and the task it landed inPop out in the code editor (opens in a new tab)JavaScript
// Asha's tap on the one-task page, from the course's recording. Times in ms since the page started.const task = { start: 1060.8, end: 1471.7 };const tap = 1152.2; console.log(`reviews.js's task ran for ${(task.end - task.start).toFixed(1)} ms.`);console.log(`Asha tapped ${(tap - task.start).toFixed(1)} ms into it.`);console.log(`Her tap waited ${(task.end - tap).toFixed(1)} ms for the rest.`);
  • Line 1 is a comment. Times are in milliseconds (ms) since the page started. A millisecond is a thousandth of a second.
  • Line 2 holds when reviews.js's task started and ended. A task is one piece of work the main thread picks up and runs to the end. The main thread is the one thread that runs the page's JavaScript and draws its frames.
  • Line 3 holds the moment Asha tapped.
  • Lines 5 to 7 print reviews.js's task ran for 410.9 ms., Asha tapped 91.4 ms into it. and Her tap waited 319.5 ms for the rest.

Here is what Asha saw. These are real frames Chrome captured. A frame is one picture the browser draws on the screen.

Asha taps Add on Mint tea at 1,152.2 ms. Real frames: at the tap, the list that has been on screen since 124.8 ms, with Cart: 0; at 1,524.7 ms, Cart: 1. Under them, the main thread: reviews.js's one task runs from 1,060.8 ms to 1,471.7 ms, and the tap waits 319.5 ms for the rest of it.1,152.2 ms: she taps AddThe tea list with Cart: 0, as Asha taps Add on Mint teaon screen since 124.8 ms1,524.7 msThe tea list with Cart: 1 and the reviews line filled inCart: 1Main threadtapreviews.js: 410.9 msthe tap waits 319.5 ms
Real frames and times from the course's recording, in ms since the page started. Asha tapped 91.4 ms into reviews.js's task. Nothing could handle her tap until the task ended, 319.5 ms later.

The list had been on screen for a second. It looked ready. But the main thread was busy, and a browser can't stop a task halfway. web.dev puts it plainly: "the browser cannot interrupt a task that's in progress." So her tap waited for the rest of it. Why it matters: a page that looks ready isn't ready while a long task runs.

Real-life analogyOne shared photocopier

Meera starts copying a 400-page report on the office's one photocopier. Ravi walks up with a single page. The machine won't stop halfway, so he waits until her whole job is done. If Meera copies 50 pages at a time and steps aside between batches, Ravi waits for one batch at most.

In real life: The office's one photocopier
In the browser: The main thread
In real life: Meera's 400-page report, copied in one go
In the browser: reviews.js as one long task
In real life: Ravi with a single page to copy
In the browser: Asha's tap
In real life: Copying 50 pages, then stepping aside
In the browser: The same work as short tasks
The main idea

A long task keeps the main thread busy for 50 ms or more. A tap that arrives during a task waits for the rest of it. Split the same work into short tasks, and each tap waits for one short task at most. The work doesn't shrink. The waits do.

Two lessons elsewhere on the site look at the same rule from other sides. The JavaScript lesson Keeping the main thread responsive shows it with small programs. The system design lesson Runtime performance shows how to plan a whole app around it. This lesson measures it.

The rest of the lesson answers Ravi's question in five steps:

  1. Why the line is drawn at 50 ms.
  2. How long a tap waits when it comes at a random moment.
  3. How Total Blocking Time adds those waits up, with no taps at all.
  4. How DevTools marks long tasks.
  5. What long animation frames see that long tasks miss.

How the course recorded it

Here is reviews.js, as the one-task page has it:

reviews.jsJavaScript
// reviews.js: reads this week's 4,000 reviews, 4,000 at a time.const BATCH = 4000;const about = document.querySelector("#about");let read = 0;for (let start = 0; start < 4000; start += BATCH) {  setTimeout(function readReviews() {    work(BATCH * 23_000);    read += BATCH;    about.textContent = "Reviews read: " + read + " of 4000";  }, 0);}
  • Line 2 sets BATCH, how many reviews each task reads. Here it's 4,000: all of them, in one task.
  • Lines 5 to 11 queue one task per batch. setTimeout(…, 0) runs a function as a new task, once the main thread is free.
  • Line 7 does the work: 23,000 steps for each review, with work() from lesson 26.3. It takes the same steps on any computer.
  • Line 9 shows how many reviews have been read so far.
  • Three pages: the same tea list, with BATCH set to 4,000, 500 or 100. That makes one task, eight tasks or forty tasks. The work is the same each time: 92 million steps.
  • Asha's phone: this computer in Chrome 157, with its processor, the CPU, slowed 4 times, as in lessons 26.2 and 26.3. The server held reviews.js back 1 second, like a slow network, so the list appeared first.
  • Loads: each page loaded three times with a trace, Chrome's detailed record of everything it did, with screenshots. The lesson quotes the middle load of each page.
  • Taps: one tap on Add, Mint tea, at each of 24 moments, 25 ms apart, on each page. That's 72 more loads.
  • Lighthouse: Lighthouse 13.6.0 loaded each page once, on the same Chrome, with the CPU slowed 4 times.

Why the line is at 50 ms

reviews.js's task ran for 410.9 ms. Is that long? The Long Tasks API, a browser feature that reports long tasks to the page, draws the line at 50 ms. Lesson 11.5 showed where that number comes from. Here it is again, as an everyday rule.

Real-life analogyThe staff-room microwave rule

The Leaf and Kettle staff room has one microwave, with a sign: "Two minutes at most." When Ravi walks up, the person using it is done within two minutes. Then his own lunch takes two minutes at most. So everyone has hot food within four minutes.

In real life: Hot lunch within four minutes
In the browser: An answer to a tap in under 100 ms
In real life: "Two minutes at most" for each person
In the browser: No task of 50 ms or more
In real life: Waiting for the person in front
In the browser: Waiting for the task already running
In real life: Heating your own lunch
In the browser: Running the tap's own handler

The goal comes from RAIL, Google's older guide to speed. It says to answer a tap within 100 ms, so the result feels immediate. Lesson 2.1 met the same 0.1 second limit. The Long Tasks spec splits those 100 ms in two:

100 ms, in two halvesPop out in the code editor (opens in a new tab)JavaScript
// The Long Tasks spec's sum: an answer in under 100 ms, in two halves. Times in ms.const goal = 100;const longestTask = 50;const handler = goal - longestTask; console.log(`Waiting for the task already running: up to ${longestTask} ms.`);console.log(`Running the tap's own handler: up to ${handler} ms.`);console.log(`With reviews.js's 410.9 ms task instead: up to ${410.9 + handler} ms.`);
  • Line 2 is the goal. Line 3 is the most a task may run.
  • Line 4 leaves the rest for the tap's own handler: the code that runs when the tap arrives.
  • Lines 6 and 7 print Waiting for the task already running: up to 50 ms. and Running the tap's own handler: up to 50 ms.
  • Line 8 prints With reviews.js's 410.9 ms task instead: up to 460.9 ms.

The spec says it in one line: a page without long tasks "should respond to user input in under 100ms". Today, web.dev recommends Core Web Vitals over RAIL for setting goals. Their measure of taps, INP, calls 200 ms or less good. But the 50 ms line for a single task stayed. Why it matters: 50 ms isn't a target. It's the most one task may take if every tap is to feel instant.

A line, not a cliff

What about a task right at the line? On the eight-task page, each task read 500 reviews. That's about 50 ms of work on Asha's phone. Some tasks came out just over the line, some just under. Here is what Chrome's Long Tasks API reported in one load:

Tasks right at the linePop out in the code editor (opens in a new tab)JavaScript
// One load of the eight-task page: the tasks Chrome's Long Tasks API reported, in ms.const reported = [58, 53, 53, 54, 51];const tasks = 8; console.log(`Long tasks reported: ${reported.length} of ${tasks}`);console.log(`Time past 50 ms: ${reported.map((ms) => ms - 50).join(" + ")} ms`);
  • Line 2 holds the five tasks it reported. Line 3 is how many tasks there were.
  • Line 5 prints Long tasks reported: 5 of 8.
  • Line 6 prints Time past 50 ms: 8 + 3 + 3 + 4 + 1 ms.

A millisecond decided which tasks were reported. But a tap that landed in any of them waited about 50 ms at most. Why it matters: going over the line by a little costs a little. Total Blocking Time counts it the same way, as you'll see.

A tap at a random moment

Asha doesn't plan when to tap. Any moment is as likely as another. So the useful question is: if she taps at a random moment, how long does she wait?

Think of the photocopier again. If Ravi walks up during Meera's job, he waits for the rest of it. He might arrive near the start, or near the end. On average, he waits for half of it. And the longer her job, the more likely he walks into it at all.

Those two ideas make a small model. A tap lands in a task with a chance equal to the task's length divided by the whole stretch of time. If it lands, it waits half the task on average:

The same work, three ways, with round numbersPop out in the code editor (opens in a new tab)JavaScript
// A tap at a random moment in 600 ms of start-up. The same 400 ms of work, three ways. Times in ms.const pages = { one: [400], eight: Array(8).fill(50), forty: Array(40).fill(10) };const windowMs = 600; for (const [name, tasks] of Object.entries(pages)) {  const average = tasks.reduce((sum, ms) => sum + (ms / windowMs) * (ms / 2), 0);  console.log(`${name}: at most ${Math.max(...tasks)} ms, ${average.toFixed(1)} ms on average`);}
  • Line 2 holds the three pages: one task of 400 ms, eight of 50 ms, or forty of 10 ms. Line 3 is the 600 ms the taps come in.
  • Line 6 adds up, for each task, its chance (ms / windowMs) times its average wait (ms / 2).
  • Line 7 prints one line per page. The first is one: at most 400 ms, 133.3 ms on average. Then come eight: at most 50 ms, 16.7 ms on average and forty: at most 10 ms, 3.3 ms on average.

Eight tasks cut the average wait by eight. Forty cut it by forty. The work didn't shrink. Each task did.

Now the recording. The course tapped Add at 24 moments, 25 ms apart, on each page. For each tap, the trace shows which task was running, and when it ended. Here is how long the taps waited for the main thread:

24 real taps on each pagePop out in the code editor (opens in a new tab)JavaScript
// 24 taps on each page, 25 ms apart. Each waited for the rest of the task it landed in. Times in ms.const taps = {  one: { average: 129.4, longest: 368.3, over50: 15 },  eight: { average: 13.7, longest: 51.5, over50: 1 },  forty: { average: 3.8, longest: 11.9, over50: 0 },};for (const [name, t] of Object.entries(taps)) {  console.log(`${name}: ${t.average} ms on average, ${t.longest} ms at most, ${t.over50} of 24 over 50 ms`);}
  • Lines 3 to 5 hold each page's average wait, its longest wait, and how many taps waited over 50 ms.
  • Line 8 prints one: 129.4 ms on average, 368.3 ms at most, 15 of 24 over 50 ms. Then comes eight: 13.7 ms on average, 51.5 ms at most, 1 of 24 over 50 ms. Last is forty: 3.8 ms on average, 11.9 ms at most, 0 of 24 over 50 ms.
Taps at 24 moments, 25 ms apart, on each page, and how long each waited for the main thread. One task: 16 taps landed in a task; 129.4 ms on average, 368.3 ms at most, 15 over 50 ms. Eight tasks: 14 taps landed in a task; 13.7 ms on average, 51.5 ms at most, 1 over 50 ms. Forty tasks: 15 taps landed in a task; 3.8 ms on average, 11.9 ms at most, 0 over 50 ms.One task129.4 ms on average368.3 ms at mostEight tasks13.7 ms on average51.5 ms at mostForty tasks3.8 ms on average11.9 ms at mostwork starts200 ms400 ms
Each bar is one tap, at the moment it came, as tall as its wait for the rest of the task it landed in. The strip under each row is one load's tasks. Same work, same moments: short tasks, short waits.

The model said 133.3, 16.7 and 3.3 ms. The recording says 129.4, 13.7 and 3.8 ms. Why it matters: you don't need Asha to know how her taps would go. The lengths of the tasks tell you.

How the taps were timed

Chrome also timed each tap itself. On the one-task page, each tap that landed in the task waited the rest of it, plus 20 ms more for the typical tap. The extra came from the tool the course tapped with: Chrome's DevTools protocol, which lets a program drive the browser. Its source code says it makes "sure the compositor is up to date before sending a touch event". So it holds each touch until the page has drawn a frame.

On the eight- and forty-task pages, that hold got in the way. Every tap that came during the work reached the page only after all the work was done. A real finger isn't held. Chrome's scheduler, the part that picks the next task, gives input its highest priority. So a real tap runs as soon as the current task ends. That's why the waits above come from the recorded tasks: each tap waits for the rest of the task it landed in.

Pick a page to see all 24 taps: when each came, which task it landed in, and how long it waited.

Playground: tap at 24 moments
One task: 4,000 reviews each24 taps
  1. 76.8 ms after the work startedin task 1waited 218.8 ms
  2. 18.8 ms after the work startedin task 1waited 368.3 ms
  3. 42.2 ms after the work startedin task 1waited 350.3 ms
  4. 74.3 ms after the work startedin task 1waited 323.5 ms
  5. 92.2 ms after the work startedin task 1waited 289.2 ms
  6. 116.8 ms after the work startedin task 1waited 289.6 ms
  7. 142.2 ms after the work startedin task 1waited 260 ms
  8. 166.2 ms after the work startedin task 1waited 193.8 ms
  9. 192.4 ms after the work startedin task 1waited 195.2 ms
  10. 215 ms after the work startedin task 1waited 170 ms
  11. 264.9 ms after the work startedin task 1waited 84.9 ms
  12. 266 ms after the work startedin task 1waited 127.5 ms
  13. 288.3 ms after the work startedin task 1waited 97.4 ms
  14. 316.1 ms after the work startedin task 1waited 69.6 ms
  15. 341.8 ms after the work startedin task 1waited 50.2 ms
  16. 366.8 ms after the work startedin task 1waited 17.4 ms
  17. 391.4 ms after the work startedbetween or after the taskswaited 0 ms
  18. 417.7 ms after the work startedbetween or after the taskswaited 0 ms
  19. 442.7 ms after the work startedbetween or after the taskswaited 0 ms
  20. 468.5 ms after the work startedbetween or after the taskswaited 0 ms
  21. 492.2 ms after the work startedbetween or after the taskswaited 0 ms
  22. 518.8 ms after the work startedbetween or after the taskswaited 0 ms
  23. 542.7 ms after the work startedbetween or after the taskswaited 0 ms
  24. 568.9 ms after the work startedbetween or after the taskswaited 0 ms
Try it yourself
129.4 ms on average, 368.3 ms at most

16 of 24 taps landed inside a task. 15 waited more than 50 ms. The rest came between tasks, or after the work, and didn't wait for any.

Worked out from the course's recordings in Chrome 157, with the CPU slowed 4 times: when each tap came, and the tasks Chrome ran in that same load. Each tap waits for the rest of the task it lands in.

Total Blocking Time adds up the bad moments

A lab tool like Lighthouse can't tap like Asha. It loads the page, and nobody touches it. So how can it tell whether taps would wait? It uses Total Blocking Time, or TBT. Lesson 3.2 introduced it: the part of each long task past 50 ms, added up. Here is why that sum is the right one.

Take one long task. A tap that lands early in it waits more than 50 ms, because more than 50 ms of the task is still to come. A tap in its last 50 ms waits less. So the stretch of a task where a tap would wait too long is its length minus 50 ms. TBT adds up those stretches. It's the total time when a tap would have waited more than 50 ms.

Real-life analogyA car park with free minutes and charging hours

A car park in town charges only from 8 in the morning to 6 at night. The first 50 minutes of each stay are free. Meera parks from 7:30 to 9:10. The meter starts at 8. It gives her 50 free minutes, then charges for the last 20.

In real life: Each stay in the car park
In Lighthouse: Each long task
In real life: Charging hours, 8 in the morning to 6 at night
In Lighthouse: From FCP to TTI
In real life: The first 50 minutes of each stay, free
In Lighthouse: The first 50 ms of each task
In real life: The minutes Meera pays for
In Lighthouse: That task's blocking time
In real life: The day's paid minutes, for every car
In Lighthouse: TBT

The charging hours are Lighthouse's window:

  • It starts at FCP, the first contentful paint: when the first text or image appears. The code behind Lighthouse's rule says there's "little risk of user input happening before FCP". There's nothing to tap yet.
  • It ends at TTI, Time to Interactive. web.dev explains that TTI waits until the main thread "has been free of long tasks for at least five seconds". Lighthouse's code also waits for the network to go quiet.

Lighthouse cuts each task to the window first, then takes off 50 ms. Here is its rule, shortened, on the one-task page:

Lighthouse's rule, shortenedPop out in the code editor (opens in a new tab)JavaScript
// Lighthouse's rule, shortened: the part of each task past 50 ms, between FCP and TTI. Times in ms.function totalBlockingTime(tasks, fcp, tti) {  let total = 0;  for (const { start, duration } of tasks) {    const from = Math.max(start, fcp);    const to = Math.min(start + duration, tti);    if (to - from > 50) total += to - from - 50;  }  return total;}// The one-task page: the page's first rendering, then reviews.js's task.const tasks = [{ start: 50.5, duration: 79.8 }, { start: 1051, duration: 411.7 }];console.log(`TBT: ${totalBlockingTime(tasks, 160, 1462.8).toFixed(1)} ms`);
  • Line 1 is a comment. Lines 2 to 10 are the rule.
  • Line 5 moves a task's start up to FCP if it began earlier. Line 6 moves its end back to TTI if it ran later.
  • Line 7 adds the part past 50 ms, if there is one.
  • Line 12 holds two tasks from the recording. The first was the page drawing itself for the first time, before FCP. The second is reviews.js.
  • Line 13 prints TBT: 361.7 ms. The first task ended before FCP, so it adds nothing.

Is the port right? The course ran it on Lighthouse's own trace of each page, with Lighthouse's FCP and TTI. It gave Lighthouse's TBT every time, to the tenth of a millisecond. For the one-task page, Lighthouse reported 341.6 ms. Its load was a different load, and its task was a little shorter.

TBT and the random taps

TBT is the time when a tap would wait over 50 ms. So TBT divided by the whole stretch is the share of random taps that wait that long. Check it against the 24 taps, which covered 600 ms:

From TBT to slow tapsPop out in the code editor (opens in a new tab)JavaScript
// TBT is the time when a tap would wait more than 50 ms. The 24 taps covered 600 ms.const windowMs = 600;const tbt = { one: 361.7, eight: 22.2, forty: 0 }; for (const [name, ms] of Object.entries(tbt)) {  const share = ms / windowMs;  console.log(`${name}: ${Math.round(share * 100)}% of moments, about ${(share * 24).toFixed(1)} of 24 taps`);}
  • Line 3 holds each page's TBT, from the course's loads.
  • Line 6 divides it by the 600 ms. Line 7 prints the share, and how many of 24 taps that makes.
  • It prints one: 60% of moments, about 14.5 of 24 taps, eight: 4% of moments, about 0.9 of 24 taps and forty: 0% of moments, about 0.0 of 24 taps.

The real taps said 15, 1 and 0. Why it matters: TBT isn't an abstract score. It's the part of the load when a tap would feel slow. Lighthouse rates up to 200 ms as fast on phones. The one-task page is far over. The eight-task page is well under.

Playground: add up the blocking time
One taskFCP 160 ms
  • A 411.7 ms task at 1,051 msadds 361.7 ms

The course's load: TBT 361.7 ms, counted up to TTI at 1,462.8 ms.

Lighthouse's own load: FCP 165.1 ms, TBT 341.6 ms.

Try it yourself
TBT: 361.7 ms in the course's load, 341.6 ms in Lighthouse's

1 task of 50 ms or more between FCP and TTI. Each adds the part of it past 50 ms.

Two different loads of each page, in Chrome 157 with the CPU slowed 4 times: one the course traced, and one run by Lighthouse 13.6.0 with DevTools throttling. The port of Lighthouse's rule gave Lighthouse's own TBT on its trace.

Long tasks in DevTools

Ravi wants to see these tasks himself. He records a load in DevTools' Performance panel, from lesson 7.1. The Main track shows every task the main thread ran, as gray bars named Task.

DevTools marks each task over 50 ms in two ways. Its docs say long tasks are "highlighted with a red triangle, and with the part over 50 milliseconds shaded in red". The shading is a pattern of red stripes. DevTools' own code starts the stripes 50 ms into the task.

Real-life analogyA spell checker's red line

A spell checker doesn't fix your writing. It puts a red line under the words to look at, so they stand out on a long page. You still read each one, and decide what to change.

In real life: The red line under a word
In DevTools: The red triangle on a task
In real life: Only words that need a look get one
In DevTools: Only tasks over 50 ms get one
In real life: Clicking the word to see suggestions
In DevTools: Clicking the task to see what ran inside

Here are the three pages, the way DevTools would draw them. The shaded ends are the stripes:

The Main track while the reviews were read, to scale, for each shape. One task: 1 task of 50 ms or more, with the part past 50 ms shaded; Total Blocking Time 361.7 ms. Eight tasks: 6 tasks of 50 ms or more, with the part past 50 ms shaded; Total Blocking Time 22.2 ms. Forty tasks: 0 tasks of 50 ms or more, with the part past 50 ms shaded; Total Blocking Time 0 ms.One taskTBT 361.7 msEight tasksTBT 22.2 msForty tasksTBT 0 mswork starts200 ms400 ms
The tasks of one load of each page, from the trace. The shaded end of a task is the part past 50 ms, which DevTools stripes in red. Add up the shaded parts, and you get each page's TBT.

The one-task page has one big stripe. The eight-task page has thin slivers. The forty-task page has none. Add up the stripes, and you get each page's TBT.

To see what ran inside a long task, click it. web.dev suggests the Bottom-Up tab, grouped by activity, to see what took the most time. Lesson 7.2 read a trace this way. Why it matters: DevTools draws the same 50 ms line as Lighthouse. So the stripes show where TBT comes from, task by task.

Where long tasks and long animation frames disagree

The forty-task page has no long tasks, and a TBT of 0. Is it perfect? Look at what Asha saw while the reviews were read:

The forty-task page while the reviews were read. Real frames show the line at 900 read at 1,178.2 ms, 1900 read at 1,284.4 ms, 2900 read at 1,393.8 ms, 3900 read at 1,505 ms. Under them: 40 tasks of about 10 ms, no long tasks, and 4 long animation frames of 105.6 ms, 111.2 ms, 107.4 ms, 107.7 ms.1,178.2 msThe tea list, with Reviews read: 900 of 4000900 read1,284.4 msThe tea list, with Reviews read: 1900 of 40001900 read1,393.8 msThe tea list, with Reviews read: 2900 of 40002900 read1,505 msThe tea list, with Reviews read: 3900 of 40003900 readTasksLong tasksnoneFrames105.6 ms111.2 ms107.4 ms107.7 ms
Real frames from the course's recording. Each frame waited for about ten short tasks, so the line jumped by about 1,000 reviews at a time. The Long Tasks API reported nothing. Long Animation Frames reported 4 long frames.

The line jumped from 900 to 1,900, then to 2,900 and 3,900. It never showed 100, 200 or 300. Each task changed it, but Chrome drew a new frame only after about ten tasks.

Lesson 11.5 met the API that sees this: Long Animation Frames, or LoAF. A long animation frame is a stretch of 50 ms or more, from when the main thread starts work until it draws the next frame. Here is what each API reported on the forty-task page:

The forty-task page, two waysPop out in the code editor (opens in a new tab)JavaScript
// The forty-task page, while the reviews were read: what each API reported. Times in ms.const longTasks = [];const frames = [  { duration: 105.6, blockingDuration: 0, scripts: 9 },  { duration: 111.2, blockingDuration: 0, scripts: 10 },  { duration: 107.4, blockingDuration: 0, scripts: 10 },  { duration: 107.7, blockingDuration: 0, scripts: 10 },];console.log(`Long tasks: ${longTasks.length}`);console.log(`Long animation frames: ${frames.length}, up to ${Math.max(...frames.map((f) => f.duration))} ms`);console.log(`Blocking: ${frames.reduce((sum, f) => sum + f.blockingDuration, 0)} ms`);
  • Line 2: the Long Tasks API reported nothing.
  • Lines 3 to 8: four long animation frames. Each lists how many scripts ran in it, and its blocking duration: how long it could have kept a tap waiting, from lesson 11.5.
  • Lines 9 to 11 print Long tasks: 0, Long animation frames: 4, up to 111.2 ms and Blocking: 0 ms.
Real-life analogyThe specials board between chores

Meera keeps the café's specials board up to date between small chores. Each chore takes a minute. A customer with a question gets an answer between any two chores. But Meera only steps over to the board every ten chores. So the board is always ten minutes behind.

In real life: Each one-minute chore
In the browser: Each 10 ms task
In real life: Answering a question between chores
In the browser: A tap gets in between tasks: blocking duration 0
In real life: Updating the board every ten chores
In the browser: A new frame after about ten tasks
In real life: A board that's always ten minutes behind
In the browser: A long animation frame

Both APIs are right. They measure different things:

  • The Long Tasks API times each task on its own. None reached 50 ms.
  • Long Animation Frames time how long the screen waited for its next picture. Chrome's guide warns about this case. A frame "could be made up of several tasks smaller than this 50 millisecond limit, yet collectively still block the browser's ability to render."

The blocking duration is 0 because no task was long. A tap would have got in between tasks. But the screen still stood still for over 100 ms at a time. For a line of text, that's fine. For an animation, it would stutter. Why it matters: long tasks and TBT tell you about taps. Long animation frames also tell you about the screen.

Drawing is a task of its own

The two APIs also disagree about drawing. Chrome's guide explains that rendering happens "in separate tasks" from the script that changed the page. The page's first rendering shows this. On the one-task page, it ran as one task of 79.8 ms, mostly working out the layout. DevTools gave it a red triangle. Long Animation Frames reported it too, at the end of an 80.1 ms frame. But the page's Long Tasks observer got no entry for it. And TBT ignored it, because it came before FCP.

Why it matters: if your page is slow to draw, the Long Tasks API may not say so. Long animation frames will.

What each tool saw

Read this table across. Each row is one page, while its reviews were read, in the load the lesson quotes. The last column is the longest of the 24 taps, worked out from the tasks.

One task, eight or forty, as each tool saw them
Shape of the workLong Tasks APITotal Blocking TimeLong animation framesLongest wait for a tap
One task of 4,000 reviews.1 entry, up to 411 ms.361.7 ms.1 frame, up to 413.4 ms.368.3 ms.
Eight tasks of 500 reviews.5 entries, up to 58 ms.22.2 ms.4 frames, up to 110.1 ms.51.5 ms.
Forty tasks of 100 reviews.No entries.0 ms.4 frames, up to 111.2 ms.11.9 ms.

That's Ravi's answer. Forty tasks cut the longest wait from 368.3 ms to 11.9 ms, and TBT from 361.7 ms to 0. The screen still waited about 100 ms for each new frame. How to split work so taps and frames both get a turn is the next lesson's subject.

Try the three shapes on this page's own main thread, and see what your browser reports:

Playground: long tasks and long frames in your browser
What Run doesJavaScript
// What the panel runs. busy(ms) keeps the main thread working until the time is up.const busy = (ms) => { const end = performance.now() + ms; while (performance.now() < end) {} };for (let i = 0; i < tasks; i++) {  setTimeout(() => {    busy(ms);    output.textContent = `Task ${i + 1} of ${tasks} done`;  }, 0);}
OutputOne 200 ms task

Nothing run yet.

Try it yourself
What your browser reported

Pick a shape, then press Run. Each shape is 200 ms of work on this page's main thread, and each task changes the line below.

Real tasks on this page's main thread, measured by your browser. Chrome reports both kinds of entry. Firefox and Safari report neither, so there the panel only times the tasks. Numbers change from run to run.

Does it add to TBT?

Each task below is on a page Lighthouse is loading. Sort it by whether it adds to Total Blocking Time:

Does it add to TBT?
  • A 120 ms task after the first paint, before TTI
  • A 45 ms task after the first paint
  • A 200 ms task that ends before the first paint
  • A 90 ms task that starts 20 ms before the first paint
  • Forty 10 ms tasks in a row
  • An 80 ms layout task after the first paint
  • A 300 ms task, 10 seconds after TTI
Try it yourself
0 of 7 correct

Sort each task by whether Lighthouse adds any of it to TBT.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "A task under 50 ms doesn't make a tap wait." No. A tap waits for the rest of whatever task is running. Under 50 ms, that rest is just shorter.
  • "Splitting the work makes it finish sooner." No. It's the same work. The forty tasks ran for 448.8 ms from the first to the last, against 411.7 ms for the one task.
  • "TBT is how long taps waited." No. Nobody taps during a Lighthouse load. TBT is the time a tap would have waited more than 50 ms.
  • "No long tasks means a smooth screen." No. The forty-task page had none, and four long animation frames of over 100 ms.
  • "Every long task adds to TBT." No. Only the part past 50 ms, between FCP and TTI. The first rendering came before FCP and added nothing.
  • "DevTools stripes the whole long task." No. The stripes start 50 ms in. Only the part past 50 ms is striped.

Practice exercises

Answer first, then use the hints.

Exercise 1 · Warm-upThe share of slow taps

One 300 ms task runs somewhere in 1,000 ms of start-up. What does the program print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
// One 300 ms task, somewhere in 1,000 ms of start-up. Taps come at random moments.
const task = 300;
const windowMs = 1000;
const overFifty = Math.max(0, task - 50) / windowMs;
console.log(`${Math.round(overFifty * 100)}% of taps wait more than 50 ms`);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upCut at FCP and TTI

    Three tasks, with FCP at 1,000 ms and TTI at 1,470 ms. What does the program print?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    // Three tasks, with FCP at 1000 ms and TTI at 1470 ms.
    function totalBlockingTime(tasks, fcp, tti) {
      let total = 0;
      for (const { start, duration } of tasks) {
        const from = Math.max(start, fcp);
        const to = Math.min(start + duration, tti);
        if (to - from > 50) total += to - from - 50;
      }
      return total;
    }
    const tasks = [{ start: 900, duration: 150 }, { start: 1200, duration: 45 }, { start: 1400, duration: 70 }];
    console.log(`TBT: ${totalBlockingTime(tasks, 1000, 1470)} ms`);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeTaps that landed

      In the taps playground, pick Forty tasks. How many of the 24 taps landed inside a task?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeLighthouse's own count

        In the TBT playground, pick Eight tasks. What TBT did Lighthouse report, in ms?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeWhat started each task?

          Open the course's trace of the eight-task page in Chrome's DevTools. What does DevTools call the event right under each long Task?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeLighthouse's quiet network

            For Time to Interactive, how many network requests may still be in flight while Lighthouse calls the network quiet?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask which task a tap would land in, and how much of it would still be left.

              The long tasks quiz · 7 questionsScore: first tries count
              1. Question 1 of 7Why does the Long Tasks API draw its line at 50 ms, when the goal is an answer in 100 ms?

                Choose an answer to see the explanation.

              2. Question 2 of 7Asha taps at a random moment while one 400 ms task runs. On average, how long does her tap wait for it?

                Choose an answer to see the explanation.

              3. Question 3 of 7What does this TBT program print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                // Two tasks, with FCP at 150 ms and TTI at 460 ms.
                function totalBlockingTime(tasks, fcp, tti) {
                  let total = 0;
                  for (const { start, duration } of tasks) {
                    const from = Math.max(start, fcp);
                    const to = Math.min(start + duration, tti);
                    if (to - from > 50) total += to - from - 50;
                  }
                  return total;
                }
                const tasks = [{ start: 100, duration: 120 }, { start: 400, duration: 60 }];
                console.log(`TBT: ${totalBlockingTime(tasks, 150, 460)} ms`);

                Choose an answer to see the explanation.

              4. Question 4 of 7The same work runs as forty 10 ms tasks, each changing the page. Which is true?

                Choose an answer to see the explanation.

              5. Question 5 of 7In DevTools' Main track, what does the striped red part of a task show?

                Choose an answer to see the explanation.

              6. Question 6 of 7Where should you measure TBT?

                Choose an answer to see the explanation.

              7. Question 7 of 7On the one-task page, the first rendering ran as one long task, before the first contentful paint. Which tool reported it?

                Choose an answer to see the explanation.

              Key takeaways

              • A long task keeps the main thread busy for 50 ms or more. A tap that lands in one waits for the rest of it.
              • The line is at 50 ms so that the wait, plus the tap's own handler, fits in 100 ms.
              • At a random moment, a tap waits half a task on average. Split the work, and each wait shrinks with the tasks.
              • TBT is the time a tap would have waited more than 50 ms, from FCP to TTI. Lighthouse measures it with no taps at all.
              • DevTools marks each task over 50 ms with a red triangle, and stripes the part past 50 ms.
              • Long animation frames also catch slow screens made of short tasks, and drawing work the Long Tasks API misses.

              Remember the one-liner.
              A tap waits for the rest of whatever task is running, so keep every task short, and read TBT as the time a tap would wait too long.

              Sources

              The facts on this page come from these specs, docs and source files:

              Coming next: Yielding to the main thread. It shows how to give the browser a turn between pieces of work, so taps and frames get in.

              CompleteFrontend Clear concepts. Working examples.