Skip to main content

Blocking the event loop

Blocking the event loop is the situation where JavaScript runs a long synchronous operation and, until it finishes, no other event is handled. The language is single threaded, so a heavy loop or a synchronous file read stops rendering, timers, promises and network callbacks alike.

Theory

TL;DR

  • The event loop runs tasks one after another: callbacks, promises, timers, events.
  • JavaScript is single threaded, only one instruction runs at any moment in time.
  • A long synchronous operation means everything else sits in the queue and waits.
  • In the browser that is a frozen UI: no reaction to clicks, scrolling or typing.
  • In Node.js that is a server that does not respond, because every request is stuck behind one CPU heavy task.
  • The cures: chunking, Web Workers, Worker Threads or child_process, and asynchronous APIs instead of synchronous ones.

Quick example

javascript
console.log('Start'); for (let i = 0; i < 1e9; i++) {} // blocks the event loop console.log('End');

While that loop runs:

  • the browser does not react to clicks, scrolling or typing;
  • timers and promises do not run;
  • the UI hangs.

What the event loop is

The event loop is the mechanism that:

  • runs tasks in order (callbacks, promises, timers, events);
  • handles asynchronous operations;
  • is responsible for how the interface reacts and how responsive the application feels.

JavaScript is a single threaded language: only one instruction runs at any moment in time. If something takes a long time, everything else sits in the queue and waits.

Why this is bad

1. A frozen interface

The user cannot interact with the site: buttons, inputs, animations, everything is frozen.

2. Asynchronous operations do not run

Neither setTimeout, nor fetch, nor Promise.then will fire until the blocking finishes.

javascript
setTimeout(() => console.log('1s elapsed'), 1000); const end = Date.now() + 5000; while (Date.now() < end) {} // blocks for 5 seconds

Result:

text
(after 5 seconds) 1s elapsed

The timer fired late: its callback was queued on time, but the queue was not being processed.

3. Lost throughput in Node.js

If a Node.js server runs a CPU heavy task (parsing a large JSON payload, encryption and so on), the remaining requests queue up and the server stops responding.

How to avoid blocking

  1. Split the computation into chunks:

    javascript
    function heavyTask() { let i = 0; function step() { while (i < 1e9 && i % 1e5 !== 0) i++; if (i < 1e9) setTimeout(step); // hand control back to the event loop } step(); }
  2. Use Web Workers in the browser. For heavy computation run the code in a separate thread so it does not disturb the main one.

  3. In Node.js use Worker Threads or child_process:

    javascript
    import { Worker } from 'worker_threads'; new Worker('./heavy-task.js');
  4. Do not use synchronous I/O methods:

    • fs.readFileSync, bad;
    • fs.readFile, good;
    • crypto.pbkdf2Sync, bad;
    • crypto.pbkdf2, good.

In short

ReasonWhat happens
JavaScript is single threadedEverything runs in one thread
Blocking codeDelays the event loop
The UI stops reactingThe interface freezes
The server hangsRequests are not processed
The fixAsynchrony, chunking, workers

Common mistakes

  • Believing async/await makes code non blocking. await only yields while waiting; a heavy synchronous computation inside an async function blocks the thread just the same.
  • Synchronous APIs on the server. fs.readFileSync, crypto.pbkdf2Sync or JSON.parse over tens of megabytes inside a request handler stop the whole process, not just that one request.
  • Trusting setTimeout to be precise. A timer guarantees "no sooner than", not "exactly after": if the event loop is busy, the callback waits.
  • Chunking with Promise.resolve().then(). That is a microtask, it runs in the same tick and never lets the browser paint a frame. You need a macrotask: setTimeout, MessageChannel or requestIdleCallback.
  • Sending huge objects to a Web Worker without Transferable. Structured cloning of a large buffer blocks the thread by itself; for an ArrayBuffer transfer ownership instead.
  • Optimising blindly. Profile first (the Performance tab, or --cpu-prof in Node.js), and only then rewrite.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.