What Is the Event Loop?
JavaScript is single-threaded, meaning it can only execute one piece of code at a time. However, it can handle asynchronous operations through the event loop. Think of it like a restaurant with only one waiter: the waiter (JavaScript) takes orders (code execution) one at a time, but the kitchen (browser APIs/node APIs) can work on multiple orders simultaneously.
The event loop constantly monitors the call stack and the task queue (also called the callback queue). When the call stack is empty, it takes the first task from the queue and executes it.
The Call Stack: Where Code Executes
The call stack is where JavaScript keeps track of function execution. When you call a function, it is added to the stack; when it returns, it is removed.
Microtasks vs. Macrotasks
Understanding the difference between microtasks and macrotasks is crucial for understanding async code execution order:
Macrotasks (Tasks)
- setTimeout
- setInterval
- setImmediate (Node.js)
- I/O operations
- UI rendering
Microtasks
- Promise.then(), catch(), finally()
- async/await (which uses Promises)
- MutationObserver
- queueMicrotask()
Key Rule: Microtasks are executed before macrotasks, and ALL microtasks in the queue are processed before moving to the next macrotask.
The Async/Await Connection
async/await is syntactic sugar built on top of Promises. Every await essentially creates a microtask.
Common Interview Questions
Question 1: What gets logged?
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
Answer: 1, 4, 3, 2
The synchronous code runs first (1, 4), then the microtask (3), then the macrotask (2).
Question 2: What about this?
const promise = new Promise((resolve) => {
console.log(1);
resolve();
});
promise.then(() => console.log(2));
console.log(3);
Answer: 1, 3, 2
The Promise executor runs synchronously, so 1 logs first. Then synchronous code continues, logging 3. Finally, the .then() callback runs as a microtask.
How the Event Loop Works (Step by Step)
- Execute all synchronous code in the call stack
- When the stack is empty, check the microtask queue
- Execute ALL microtasks (until the queue is empty)
- Perform any rendering (browser) or process I/O (Node.js)
- Take ONE macrotask from the queue and execute it
- Repeat from step 2
Key Takeaways
- JavaScript is single-threaded but handles async through the event loop
- Microtasks (Promises) run before macrotasks (setTimeout, I/O)
- All microtasks in the queue complete before any macrotask runs
- Understanding this order is crucial for debugging async code
- async/await makes Promise code cleaner but follows the same rules
Understanding the event loop will help you write better asynchronous JavaScript and ace your technical interviews!