The event loop is the rule JavaScript uses to decide what runs next whenever more than one piece of code is waiting to execute. Rather than memorizing the output of specific examples, it is faster in the long run to learn the three places code can sit: the call stack, the microtask queue, and the callback queue, and the fixed order the engine checks them in. Once that order is automatic, predicting the output of any snippet, including the ones interviewers like to ask about, becomes mechanical instead of guesswork.
What changed in 2026
- DevTools now visualize this directly. Chrome and Firefox performance panels show the call stack, microtask queue, and task queue as separate lanes, turning a formerly abstract mental model into something you can actually watch happen on a real page.
- Alternative runtimes converged on the same ordering. Bun and Deno both implement the same microtask-before-macrotask ordering as Node.js and browsers, so the mental model in this guide transfers across runtimes even though the underlying implementation differs.
- Execution-order questions remain a fixture of interviews. Predicting console output across timers and promises is still common in front-end interviews in 2026, precisely because it is quick to ask and reveals whether a candidate has a real mental model or a memorized answer.
The three places code can be
Code is either running on the call stack right now, waiting in the microtask queue, or waiting in the callback queue, sometimes called the macrotask queue. The call stack holds synchronous code as it executes. The microtask queue holds promise callbacks and anything scheduled with queueMicrotask. The callback queue holds timer callbacks, I/O callbacks, and UI events. The rule that ties them together: the call stack must fully empty, then every pending microtask runs, including new ones scheduled along the way, then exactly one macrotask runs, and the cycle repeats.
Trace this snippet
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
console.log("1") runs immediately on the call stack. setTimeout schedules its callback on the callback queue and returns immediately, logging nothing yet. Promise.resolve().then(...) schedules its callback on the microtask queue and also returns immediately. console.log("4") runs immediately on the call stack. Only once the stack is empty does the event loop drain the microtask queue, logging 3. Only after the microtask queue is empty does it take exactly one macrotask, logging 2. The final order is 1, 4, 3, 2, not the order the lines appear in and not the order they were scheduled in.
What goes where
| API |
Queue |
Runs relative to synchronous code |
| Plain top-level code |
Call stack |
Immediately, in order |
Promise.then, queueMicrotask, resuming after await |
Microtask queue |
After the stack empties, before any macrotask |
setTimeout, setInterval |
Callback (macrotask) queue |
After the stack empties and all microtasks are drained |
| DOM and UI events, I/O callbacks |
Callback (macrotask) queue |
Same tier as setTimeout |
The rule that predicts every snippet
Run all synchronous code on the call stack until it is empty. Drain the entire microtask queue, including any new microtasks scheduled by microtasks that just ran. Run exactly one macrotask. Return to draining microtasks. Applied mechanically, this four-step loop predicts the output of any ordering question, including nested promise chains and timers scheduled inside other timers.
Common mistakes
Assuming a zero-delay setTimeout runs immediately is the most common one; it still waits for the stack to empty and every pending microtask to drain first, which is often a measurable amount of real time later. A long promise chain can starve a timer callback, because a microtask that schedules another microtask lets that new one run before the next macrotask gets a turn. Treating async and await as a separate model is also a mistake: the code after an await resumes as a microtask, following exactly the rules above. See callbacks vs promises vs async await for how all three styles map onto this same underlying schedule.
FAQ
Is the event loop part of the JavaScript language itself?
No. It is provided by the runtime, the browser or Node.js, rather than the language specification, though the specification does define how promise microtasks behave.
Why do microtasks always run before macrotasks?
By design. The intent behind promises was for a resolved value to feel close to immediate, so its callback jumps ahead of anything scheduled with a timer, even a zero-delay one.
Can the call stack and event loop run at the same time?
No. Only one runs at a time on a given thread. The event loop only checks its queues once the call stack is completely empty, which is why a long synchronous function blocks everything else.
Is this the same event loop model in Node.js and the browser?
The stack-then-microtasks-then-macrotask rule is the same, but Node.js adds extra macrotask phases and its own early queue that runs before other microtasks. The core mental model described here still applies in both.
Where to go next