1. Introduction
The event loop is the mechanism that allows JavaScript -- a single-threaded language with one call stack -- to handle asynchronous operations like timers, network requests, and Promise callbacks without blocking. Understanding it precisely is essential for correctly predicting the order in which asynchronous code executes, which is one of the trickiest and most commonly misunderstood areas of JavaScript.
Cricket analogy: A bowler (call stack) can only bowl one delivery at a time, but the ground staff (Web APIs) track a DRS review in the background so the umpire doesn't have to stall the game waiting for it.
The runtime consists of a few key pieces: the call stack (where currently executing function calls live), Web APIs / Node APIs (which handle timers, I/O, and network operations outside the main thread), and two kinds of queues that feed completed work back into the call stack -- the macrotask queue (also called the task or callback queue) and the microtask queue.
Cricket analogy: The playing XI on the field is the call stack, the dressing room's analysts running instant replays are the Web APIs, and two lists -- over-by-over milestones (macrotask) and quick no-ball rechecks (microtask) -- feed results back to the umpire.
2. Syntax
// Macrotask sources
setTimeout(() => {}, 0);
setInterval(() => {}, 1000);
// Microtask sources
Promise.resolve().then(() => {});
queueMicrotask(() => {});
// Simplified event loop algorithm:
// 1. Run the current synchronous script to completion.
// 2. Drain the ENTIRE microtask queue (including any new microtasks
// queued while draining it) before doing anything else.
// 3. Take ONE macrotask from the queue and run it.
// 4. Drain the microtask queue again.
// 5. Repeat from step 3.3. Explanation
The call stack executes synchronous code immediately, in order. When you call an asynchronous API like setTimeout or fetch, the actual work is delegated outside the call stack; once it's ready, its callback is placed into the appropriate queue rather than being run right away. The event loop's job is to move work from these queues back onto the call stack, but ONLY when the call stack is completely empty.
Cricket analogy: The umpire only signals the next ball once the current delivery, run, and appeal are fully resolved on the pitch -- a DRS request raised mid-delivery waits until the call stack (live action) is completely empty.
The crucial rule that trips up most developers is the priority between the two queues: after every single macrotask finishes (including the very first run of the main script), the JavaScript engine fully drains the ENTIRE microtask queue -- running every microtask, including any new ones that get queued by earlier microtasks -- before it is allowed to pick up the next macrotask. Promise .then/.catch/.finally callbacks and queueMicrotask() go into the microtask queue. setTimeout, setInterval, and I/O callbacks go into the macrotask queue.
Cricket analogy: After every over (macrotask) is bowled, the umpire must clear every pending DRS review (microtask) -- even new reviews raised during those reviews -- before the next over can start, just like promise callbacks always jumping ahead of the next setTimeout over.
This is why Promise.resolve().then(() => console.log('microtask')) always logs before setTimeout(() => console.log('macrotask'), 0), even though both were scheduled 'immediately' -- the entire microtask queue is emptied before the event loop is even allowed to look at the macrotask queue again, regardless of the requested setTimeout delay.
4. Example
console.log("1 - script start");
setTimeout(() => {
console.log("2 - setTimeout callback");
}, 0);
Promise.resolve()
.then(() => {
console.log("3 - promise 1");
})
.then(() => {
console.log("4 - promise 2");
});
console.log("5 - script end");5. Output
1 - script start
5 - script end
3 - promise 1
4 - promise 2
2 - setTimeout callback6. Key Takeaways
- The call stack runs synchronous code first, always, before any queued async callback.
- The microtask queue (Promises, queueMicrotask) is fully drained before the engine ever looks at the macrotask queue.
- The macrotask queue (setTimeout, setInterval, I/O, UI events) is processed one task at a time, with a full microtask drain after each one.
- A setTimeout(fn, 0) callback ALWAYS runs after all currently pending microtasks, no matter how many there are.
- New microtasks queued during microtask processing are still processed before the next macrotask -- microtasks can 'starve' macrotasks if they keep scheduling more of themselves.
- Reliable async ordering requires knowing whether an API queues a microtask or a macrotask, not just its stated delay.
Practice what you learned
1. What must be true before the event loop can pull a new task from the macrotask queue?
2. Which of these schedules a MICROTASK rather than a macrotask?
3. Trace this code: `console.log('a'); Promise.resolve().then(() => console.log('b')); Promise.resolve().then(() => console.log('c')); setTimeout(() => console.log('d'), 0); console.log('e');` What is the output order?
4. If a .then() callback schedules ANOTHER .then() callback while the microtask queue is being drained, what happens?
5. Why does `setTimeout(fn, 0)` not run fn immediately, even though the delay is zero?
Was this page helpful?
You May Also Like
Promises in JavaScript
Understand the Promise object, its three states, and how .then/.catch/.finally chaining replaces nested callbacks.
Callbacks and Asynchronous JavaScript
Learn how JavaScript handles operations that take time using callback functions, and why deeply nested callbacks lead to callback hell.
setTimeout and setInterval in JavaScript
Schedule delayed or repeating code with setTimeout and setInterval, and learn about timer drift and the need to clear intervals.
async/await in JavaScript
Learn how async/await provides synchronous-looking syntax for working with Promises, including error handling with try/catch.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics