The event loop
JavaScript runs on one thread: one thing at a time, and while it is running nothing else can — not a click, not a timer. So anything slow (a network request, a file read, a timer) is handed to the host, and JavaScript moves on. When the slow thing finishes, its callback is put in a queue; the event loop runs the next queued callback whenever the stack is empty. Two queues matter: microtasks (promise callbacks — run first, all of them) and macrotasks (timers, I/O — one per turn).
You should see
1: sync start
2: sync end
2b: after blocking
3: microtask
4: another microtask
5: timeout 0ms
6: timeout 10msPromise.resolve().then(...) inside the first setTimeout callback. Where does it print? (Microtasks drain after every macrotask.)console.log("A")setTimeout(() => console.log("D"), 0)Promise.resolve().then(() => console.log("C"))console.log("B")
Runs immediately on the stack.
stack | main |
microtasks | [] |
macrotasks | [] |
AAll 6 steps as a table
| Step | Line | What happened | Variables now |
|---|---|---|---|
| 1 | 1 | Runs immediately on the stack. | stack = main microtasks = [] macrotasks = [] |
| 2 | 2 | Hand the timer to the host. Its callback will be queued as a macrotask after ≥0 ms — not run now. | macrotasks = [D] (pending) |
| 3 | 3 | The promise is already resolved, so its .then callback is queued as a microtask — still not run. | microtasks = [C] |
| 4 | 4 | Still on the main stack. | |
| 5 | 4 | Main script finishes; the stack is empty. The event loop drains ALL microtasks first. | stack = (empty) microtasks = [] |
| 6 | 2 | Then one macrotask: the timer callback. | macrotasks = [] |
setTimeout(fn, 0) means "after everything else currently queued", why a long loop freezes the page, why await lets other work happen, and why Node can serve thousands of connections on one thread — it never waits, it queues.