How does the event loop work?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
JavaScript runs on a single thread, so it needs a way to handle slow work without blocking. The event loop is that mechanism.
The pieces
- Call stack — where synchronous code executes, one frame at a time.
- Web APIs / Node APIs — timers, network, file I/O. These run outside the engine, in the host.
- Macrotask queue — callbacks ready to run: timers, I/O, events.
- Microtask queue — promise callbacks,
queueMicrotask,MutationObserver.
The cycle
Run all synchronous code. When the stack empties, drain the entire microtask queue. Then take one macrotask, run it, and drain microtasks again. Between iterations, the browser may render.
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 1, 4, 3, 2
3 beats 2 even with a 0 ms timeout, because microtasks are drained before the loop moves on to timers.
Two consequences worth knowing
Microtasks are drained completely, including any queued while draining. An infinite promise chain starves the loop — timers never fire and the page never repaints:
function loop() { Promise.resolve().then(loop); }
loop(); // freezes the tab
And a long synchronous task blocks everything. async does not help here; only breaking the work into chunks that yield, or a Web Worker, will.
Timers are a minimum, not a promise
setTimeout(fn, 100) means "not before 100 ms". If the stack is busy, it runs later. setTimeout(fn, 0) is clamped to at least 1 ms.
In practice
This explains why rendering stutters during heavy computation, why await feels instant (the continuation is a microtask), and why a 50 ms task is already long enough for users to notice.
