A CPU-heavy loop blocks the JavaScript event loop

Read the explanation
What if

A ticker logs every 20 ms. A 200 ms busy loop holds the only JS thread, so no tick runs until it ends.

Predict first

A ticker logs every 20 ms. Then a 200 ms busy loop runs. How many ticks print during the loop?

012345678910tickJS thread: slow work startsstartsbusy loop: blocks the threadJS thread: slow work endsendstickticktick is due, waiting for the thread

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
Step 1/4

A ticker logs every 20 ms.

1const ticker = setInterval(() => console.log("tick"), 20);2 3setTimeout(() => {4  console.log("slow work starts");5  const end = Date.now() + 200;6  while (Date.now() < end) {} // pure CPU work: blocks the only JS thread7  console.log("slow work ends");8}, 30);9 10setTimeout(() => clearInterval(ticker), 400);

Explanation

What just happened

A ticker logged every 20 ms. Then a timer callback started a 200 ms busy loop. The next tick was due, but its callback runs on the same thread as the loop, so it had to wait. No tick printed until slow work ends.

Try the what-if switch

Why

The event loop can only take the next callback when the current one returns. A long synchronous function never returns early, so everything queued behind it waits, like timers and promise callbacks.

Watch out

A Worker doesn’t share your variables. postMessage copies the data, so a change made in the worker doesn’t show up in the main thread unless you send it back.

Common questions

Is JavaScript single-threaded?

Your JavaScript runs on one thread per realm: the main thread, plus one per Worker. While a function runs, no other callback, timer or promise on that thread can run.

Does async make CPU-heavy code non-blocking?

No. An async function runs synchronously until its first await. A busy loop inside it blocks the thread just the same.

How do I run CPU-heavy work without blocking?

Move it to a Worker (node:worker_threads in Node, Web Workers in browsers). It runs on its own thread with its own event loop, and talks to the main thread through postMessage, which copies the data.

Verified: this timeline matches a real program, checked in CI on Node.js 22 and 24. How we verify. Unclear or wrong? Tell us