What you'll be able to do
By the end of this page you should be able to explain why setTimeout(fn, 0) is not instant, predict a simple microtask vs macrotask log order, and connect the event loop to async code you already write (fetch, Promise.then, async/await).
Previous post: execution context and call stack.
Who this is for
- People who logged "timeout" before "sync" and thought the timer was broken
- Beginners who finished the stack post and asked "what runs next?"
- Anyone who saw
Promise.thenbeatsetTimeout(0)in a quiz and wanted a calm explanation
You can skip this if task order already feels predictable. This closes Phase 9 — mental models.
Stack first, then queues
While your synchronous code runs, the call stack is busy — callbacks from timers or clicks wait their turn.
console.log("A");
setTimeout(function () {
console.log("B");
}, 0);
console.log("C");
// A, C, B
Scenario A — zero delay is not zero work. setTimeout(..., 0) means "queue this macrotask after the current stack clears," not "run now."
Scenario B — long sync loop. If you block the stack for three seconds, timers and clicks wait — the page feels frozen.
The rule: finish current JS on the stack; then the event loop picks the next task.
Macrotasks vs microtasks (practical names)
Macrotasks (task queue): setTimeout, setInterval, I/O callbacks, UI events — broad bucket you can call "later tasks."
Microtasks: Promise.then, queueMicrotask, async/await continuations after a await — run after the current stack, before the next macrotask.
console.log("1");
setTimeout(function () {
console.log("2 timeout");
}, 0);
Promise.resolve().then(function () {
console.log("3 microtask");
});
console.log("4");
// 1, 4, 3 microtask, 2 timeout
Scenario A — interview log order. Sync first, microtasks drain, then macrotasks.
Scenario B — fetch + then + setTimeout. Response then is microtask; timer is macrotask — UI updates often follow this pattern.
You do not need every browser internal name — remember promises before timers when both are queued during the same synchronous turn.
Why this matters for UI
button.addEventListener("click", function () {
heavySyncWork(); // blocks paint and other callbacks
});
Scenario A — click feels laggy. Stack never clears until heavySyncWork finishes.
Scenario B — split work. queueMicrotask or setTimeout(0) can yield — but the real fix for big jobs is smaller chunks or a worker, not endless setTimeout hacks.
A tiny practice file
<!DOCTYPE html>
<html>
<body>
<button id="go">Run loop demo</button>
<pre id="out"></pre>
<script>
const out = document.getElementById("out");
function log(msg) {
out.textContent += msg + "\n";
}
document.getElementById("go").addEventListener("click", function () {
out.textContent = "";
log("sync start");
setTimeout(function () {
log("macrotask timeout");
}, 0);
Promise.resolve().then(function () {
log("microtask promise");
});
log("sync end");
});
</script>
</body>
</html>
Click — order should be sync start, sync end, microtask promise, macrotask timeout.
Mistakes I see a lot
1. Expecting setTimeout(0) to run before the next line of sync code.
2. Assuming async/await is a new thread. It still schedules continuations as microtasks on the same thread.
3. Chaining infinite microtasks and wondering why the page never paints. Drain the stack, yes — but do not recurse microtasks forever.
4. Over-studying loop diagrams before running one log script. Type the four-line example once; the order sticks.
What to try before the next post
- Predict then log the 1/4/3/2 example; change to nested
thenand two timeouts. - Watch the Network tab: when does
thenrun relative toloadevents? - Build the tiny HTML file.
This closes Phase 9 — mental models. Next we begin Phase 10 — useful deeper JS: modules — named, default, and dynamic import.
Try this next outside the series
These mental models become more useful when you can point at a real framework problem they explain.
- React Server Components explained — see execution boundaries and composition decisions in a modern app
- Node.js error handling — watch call stacks and thrown errors matter in real request code