The Event Loop
In short: JavaScript runs on one thread with a single call stack —
it can only do one thing at a time. Async work doesn't run in parallel on
that thread; it gets scheduled onto one of two queues and handled once the
stack is empty. The microtask queue (Promise .then/.catch/
.finally) is always drained completely before the event loop touches
the macrotask queue (setTimeout, setInterval) — which is why a
setTimeout with a 0ms delay still runs after every microtask that was
already waiting, not before.

Sync first, then microtasks, then macrotasks
The ordering rule, shown directly — nothing here is random:
"1" and "2" are plain synchronous code — the chef does them
immediately, in order, before anything else is even considered. Once the
call stack is empty, the microtask queue is checked first: "3" runs.
Only after the microtask queue is fully empty does the event loop take
one task from the macrotask queue: "4". The setTimeout's 0ms delay
never changes this order — it only means "eligible to run as soon as
possible," not "before the microtasks."
setInterval: the same macrotask tray, repeatedly
setInterval schedules its callback onto the macrotask queue over and
over, at roughly the given interval, until you clearInterval it:
Each tick is its own trip through the macrotask tray — the chef finishes
whatever else is happening, then takes the next tick's order. Forgetting
clearInterval is a classic bug: the interval keeps firing forever, long
after you needed it.
Quick practice
Which runs first once the call stack is empty: the microtask queue or the macrotask queue?
Does setTimeout(fn, 0) run its callback before or after an already-pending Promise.then() callback?
What does forgetting to call clearInterval on a running setInterval cause?
