Behind the Scenes of the Event Loop: Microtask vs Macrotask
JavaScript might be single-threaded, but that doesn’t mean it can only do one thing at a time. The real magic happens inside the Event Loop.
Think of the Event Loop as the heartbeat of the JavaScript engine. Code doesn’t just execute linearly—it runs according to smartly managed queues. Promises, setTimeout, and even DOM updates line up in this system. Every time you say “wait” (like during fetch requests or animations), the heartbeat ticks again.
A Simple Example
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
Expected output: “1, 2, 3, 4”?Nope. The real output: 1, 4, 3, 2
Why?
Because JavaScript has two types of task queues:- Microtask Queue → Promises,
queueMicrotask, etc. - Macrotask Queue →
setTimeout,setInterval,requestAnimationFrame, etc.
- If the call stack is empty → run all microtasks.
- After microtasks → render the screen.
- Then move on to the next macrotask.
Promise callbacks run before setTimeout ones.Step-by-Step Event Loop Stages
- Call Stack: Runs synchronous code.
- Microtask Queue: Fast, lightweight tasks (Promises, async/await).
- Render Phase: Browser paints updates to the screen.
- Macrotask Queue: Timers, network callbacks, I/O events.
What Are Microtasks?
Microtasks are small but critical. They handle Promise resolutions, minor DOM updates, or jobs queued via queueMicrotask().
queueMicrotask(() => {
console.log('This runs at the end of the current event loop.');
});
They run right after the current loop iteration, before rendering. Great for tiny, immediate tasks.
⚠️ Beware of infinite microtask chains (Promise.then().then()...). They can block rendering, making your UI sluggish. 🐢
What Are Macrotasks?
Macrotasks are for “later” work—timers, events, or rendering callbacks.
setTimeout(() => console.log('Macrotask!'), 0);
Even with 0 milliseconds, this runs in the next event loop iteration. It waits for microtasks to finish. So 0 means “soon,” not “immediately.” 😊
Comparison Table
| Property | Microtask | Macrotask |
|---|---|---|
| Examples | Promise, queueMicrotask | setTimeout, setInterval, requestAnimationFrame |
| Timing | Same event loop, before render | Next event loop iteration |
| Use Case | Small, immediate jobs | Larger or delayed jobs |
| Performance | Low impact | Moderate (may delay render) |
| Priority | Higher | Lower |
In Node.js
Node’s implementation is similar but with slightly different naming: process.nextTick() behaves like a microtask, while setImmediate() behaves like a macrotask.
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Typical output: nextTick → timeout → immediate. Node.js follows the same concept with its own queues.
Real-World Analogy
Imagine a concert stage:
- Microtasks: The crew making quick light or sound adjustments between acts.
- Macrotasks: The next performer waiting to go on stage.
- Render: What the audience actually sees.
That’s why Promises (microtasks) always “perform” before
setTimeout (macrotasks).Performance and Pitfalls
- Too many microtasks block rendering. The browser can’t “take a breath.”
- Too many timeouts overload the CPU, causing frame drops.
- Mixing in slight delays (
setTimeout) between Promises can balance performance.
Conclusion
The Event Loop is JavaScript’s invisible engine. Understanding it lets you predict async behavior. The gap between Promise and setTimeout may seem small—but it makes a massive difference in how smooth your UI feels.
Use microtasks for fast operations, and macrotasks for scheduled or deferred work.