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 / .finallyawaitqueueMicrotask()
2. Macrotasks (Normal Priority)
These are regular tasks that go into the main queue.
Macrotasks include:
- User events (
click,mousemove) setTimeoutandsetInterval- 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.