Event Loop
JavaScript is single-threaded — it can only execute one statement at a time. The event loop is the mechanism that makes non-blocking async behaviour possible.
It continuously checks: is the call stack empty? If yes, it picks the next waiting callback and pushes it onto the stack.
The moving parts
| Component | Role |
|---|---|
| Call Stack | Where your code runs. Functions are pushed on, popped off when they return. |
| Web APIs / Node APIs | Browser or Node provide these (setTimeout, fetch, fs.readFile, etc.). They do work outside the JS engine. |
| Callback Queue (Task Queue) | When a Web/Node API finishes, its callback goes here to wait. |
| Microtask Queue | Promise .then() / .catch() / .finally() handlers go here. Always drained before the next task queue item. |
Execution order example
console.log('1 — sync')
setTimeout(() => {
console.log('2 — macrotask (setTimeout)')
}, 0)
Promise.resolve()
.then(() => console.log('3 — microtask (Promise)'))
console.log('4 — sync')
Output:
1 — sync
4 — sync
3 — microtask (Promise)
2 — macrotask (setTimeout)
Microtasks always run first
After every synchronous chunk, the engine drains the entire microtask queue before picking a single macrotask.
Blocking the call stack blocks everything
A long-running while loop or heavy computation stalls the event loop — no callbacks, no renders, no I/O. Offload heavy work to Workers.
Related
Asynchronous — Promises & async/await
Callbacks — the original async pattern