Microtasks vs Macrotasks: How the Event Loop Picks
You schedule a Promise and a setTimeout(0) in the same line — the Promise wins, every time. This comparison explains why microtasks drain completely before a single macrotask runs, where each model shines, where each breaks (starvation vs. sluggishness), and how Node 22 changes the rules, ending…
Write these three lines, run them, and watch your mental model break: setTimeout(() => console.log('timeout'), 0); Promise.resolve().then(() => console.log('promise')); console.log('sync'); Output: sync , promise , timeout . The Promise was scheduled *second*, the timer had a delay of *zero* — and the Promise still won. This is the microtasks vs macrotasks moment every JavaScript developer hits eventually, and it isn't a quirk or a race condition. It's the event loop working exactly as specified. The rule fits in one sentence: after synchronous code finishes, the engine drains every pending microtask before running one macrotask — then repeats. But knowing the rule and knowing *when to schedule what* are different skills. This comparison puts both scheduling models on trial: their strengths, their failure modes, and a verdict on which to reach for in real code. The choice, stated plainly Every async API in JavaScript is asking you one question: when should this code run relative to everything else? The answer determines which queue it lands in. Microtask — "run me as soon as the current code finishes, before anything else gets a turn." Promise callbacks, await continuations, queueMicrotask() , MutationObserver callbacks. Macrotask (also just called a "task") — "run me on a later turn of the loop, after pending microtasks drain and the browser has had a chance to breathe." setTimeout , setInterval , DOM event handlers, I/O callbacks. One turn of the loop looks like this: Run the current task (your synchronous script) to completion. Drain the entire microtask queue — including microtasks scheduled *while* draining. Let the browser render if it needs to. Take one macrotask off the queue and run it, then go back to step 2. That…