What is the event loop and how does JavaScript handle asynchronous code with a single thread?
Show answer — try answering out loud first
The event loop is the mechanism that lets JavaScript, which is single-threaded, handle asynchronous tasks. It first runs all the synchronous code (the call stack), then the microtasks (promises), and after that the macrotasks (setTimeout, events), repeating the cycle.
JavaScript has a single thread, so it can't do two things at once. The event loop coordinates what runs and when, using several pieces:
- Call stack: the stack where synchronous code runs, one item at a time.
- Environment APIs (browser or Node): they handle tasks that take time, like
setTimeoutor requests. - Microtask queue: resolved promises (
.then,await). These have priority. - Macrotask queue: callbacks from
setTimeout,setInterval, events.
This is what the event loop does: it runs all the synchronous code; when the call stack is empty, it drains the microtask queue completely; then it takes one macrotask, and starts over.
The key point: microtasks (promises) run before macrotasks (setTimeout), even if the setTimeout delay is 0 ms.
console.log("1 (síncrono)");
setTimeout(() => {
console.log("2 (macrotarea: setTimeout)");
}, 0);
Promise.resolve().then(() => {
console.log("3 (microtarea: promesa)");
});
console.log("4 (síncrono)");
// Output:
// 1 (síncrono)
// 4 (síncrono)
// 3 (microtarea: promesa)
// 2 (macrotarea: setTimeout)
// Explanation of the order:
// - First the synchronous code: 1 and 4.
// - Then the microtasks are drained: 3 (the promise).
// - Last, the macrotasks: 2 (the setTimeout).
Thinking that setTimeout(fn, 0) runs before a promise. It doesn't: promises are microtasks and have priority over macrotasks like setTimeout. Another mistake is believing that JavaScript is multi-threaded just because it can do things "at the same time"; in reality it delegates to the environment APIs and coordinates through the event loop.
"The event loop is what lets JavaScript, which is single-threaded, handle asynchronous code. It first runs all the synchronous code on the call stack. When the stack is empty, it processes the microtask queue, which are the promises, and drains it completely. Then it takes a single macrotask, like a
setTimeoutcallback, and repeats the cycle. The key thing is that promises have priority oversetTimeout, even if the timeout is zero milliseconds."
Order the output:
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
See answer
A, D, C, B. First the synchronous code (A, D), then the promise's microtask (C), and last the setTimeout's macrotask (B).