The Event Loop
JavaScript is single-threaded — it runs one piece of code at a time. The event loop coordinates synchronous code, asynchronous callbacks, and I/O so the program stays responsive.
Call Stack
The call stack tracks function execution. When a function is called, it is pushed; when it returns, it is popped.
function a() { b(); }
function b() { c(); }
function c() { console.log('done'); }
a();
// Stack: a → b → c → (pop c) → (pop b) → (pop a)
Web APIs / Node APIs
Operations like setTimeout, fetch, and file I/O are handled outside the main thread by the browser or Node.js. When complete, their callbacks are queued.
Task Queue (Macrotasks)
Callbacks from setTimeout, setInterval, I/O, and UI events go to the task queue (macrotask queue).
Microtask Queue
Promises (.then, .catch, .finally), queueMicrotask, and MutationObserver use the microtask queue, which has higher priority than the task queue.
How the Event Loop Works
- Run all synchronous code on the call stack until empty
- Run all microtasks until the microtask queue is empty
- Run one macrotask
- Repeat from step 2
Classic Example
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
Explanation:
1and4run synchronously- Promise callback (
3) is a microtask — runs before macrotasks setTimeoutcallback (2) is a macrotask — runs last
More Complex Example
console.log('start');
setTimeout(() => console.log('timeout 1'), 0);
Promise.resolve()
.then(() => {
console.log('promise 1');
return Promise.resolve();
})
.then(() => console.log('promise 2'));
setTimeout(() => console.log('timeout 2'), 0);
console.log('end');
// start → end → promise 1 → promise 2 → timeout 1 → timeout 2
async/await and the Event Loop
await pauses an async function and schedules the rest as microtasks:
async function example() {
console.log('A');
await Promise.resolve();
console.log('B'); // microtask
}
example();
console.log('C');
// A → C → B
Why This Matters
- Explains why
setTimeout(fn, 0)does not run immediately - Helps debug race conditions and ordering bugs
- Clarifies why Promise callbacks run before
setTimeout - Foundation for understanding performance and UI blocking
Blocking the Event Loop
Long synchronous tasks block everything — no rendering, no callbacks:
// Bad: blocks for 5 seconds
let start = Date.now();
while (Date.now() - start < 5000) {}
// Better: break into chunks or use Web Workers
Understanding the event loop is key to writing efficient asynchronous JavaScript.