The Execution Queue

JavaScript is single-threaded — it can only do one thing at a time. To handle asynchronous operations, it uses a task queue system. All tasks are divided into two priority levels.

1. Microtasks (Highest Priority)

These execute immediately after the current code finishes, before the browser does anything else (including rendering).

Microtasks include:

  • Promise.then / .catch / .finally
  • await
  • queueMicrotask()

2. Macrotasks (Normal Priority)

These are regular tasks that go into the main queue.

Macrotasks include:

  • User events (click, mousemove)
  • setTimeout and setInterval
  • Network requests (when they complete)
  • Script parsing

The Golden Rule

The microtask queue must be completely empty before the browser moves on to the next macrotask or renders a new frame.

setTimeout(() => console.log('A - Macrotask (setTimeout)'), 0);

Promise.resolve().then(() => console.log('B - Microtask (Promise)'));

console.log('C - Synchronous code');

// Output order: C → B → A
Caution

Creating an infinite chain of microtasks will freeze the browser just as effectively as an infinite loop — because macrotasks (and rendering) will never get a turn.