Skip to content
Performance Lab · LAB-05long tasks / frame budget

Main-thread blocking experiment

Run real work on your real main thread and watch your real frame rate fall over. No simulation.

Level
ENGINEER
Read time
08 min
Experiment
Available
Type
MEASUREMENT

The question

What does 'blocking the main thread' feel like, measured rather than described, and which escape hatches actually help?

Hypothesis

Any single task longer than ~50ms is a long task. Above ~200ms, input feels broken. Splitting the same total work into small chunks keeps the page responsive without making it finish sooner.

Method

Laboratory available

The instrument for this investigation runs in the laboratory, where the controls, the live model and the observation log share one workstation.

Enter the laboratory

This bench runs genuine synchronous work on your main thread and measures the frames that were lost. Compare a single blocking task with the same work split into chunks, and with the same work moved to a worker.

What we observed

Task lengthPerceived as
< 16msFree, fits in a frame
50msA long task by definition; one dropped frame or two
100msNoticeable hesitation on click
300msBroken. Users click again.
1000ms+Users assume the page crashed

Why it happens

The main thread runs JavaScript, style, layout, paint and most event handling. While a task runs, none of the others can, the page cannot even repaint a pressed button state. Chunking does not reduce total work; it inserts rendering opportunities so the browser can service input and paint between chunks.

example.jsjs
// 1. Yield explicitly (Chromium; fall back to a task split elsewhere)for (const chunk of chunks) { process(chunk); if (navigator.scheduling?.isInputPending?.()) await scheduler.yield();}// 2. Split across tasks, works everywhereconst yieldToMain = () => new Promise((r) => setTimeout(r, 0));// 3. Move it off the thread entirelyconst worker = new Worker(new URL("./heavy.worker.js", import.meta.url), { type: "module" });worker.postMessage(payload);
Three ways to stop blocking
The Why MachineDEPTH 1 / 4

Why can't the browser just interrupt my function?

  1. Because JavaScript on the main thread has run-to-completion semantics.

Further research

The Long Animation Frames API (LoAF) reports frames that took too long together with attribution: which script, which source location, how much was style and layout. It is significantly more actionable than the older Long Tasks API, which could tell you that something took 300ms but not what.

References

  1. 01web.devOptimize long tasks
  2. 02web.devLong Animation Frames API
  3. 03MDNUsing Web Workers