Book 1 · Lesson 1.2
Call stack, task queue, microtasks
- Separate call stack from task and microtask queues
- State the drain order when the stack is empty
- Debug a simple race between timeout and promise
Prerequisites: 1.1
Three structures drive async JavaScript:
| Structure | Holds | Processed when |
|---|---|---|
| Call stack | Currently running function | Immediately (LIFO) |
| Microtask queue | Promise.then, queueMicrotask | Stack empty — all microtasks, repeatedly |
| Macrotask queue | setTimeout, I/O, UI events | One per loop turn after microtasks |
Drain algorithm (simplified)
- Run sync code until stack is empty
- Run every microtask until micro queue empty
- Run one macrotask
- Repeat
That is why C beats B in the classic demo.
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');Run synchronous code. console.log('A') executes immediately on the call stack.
Call stack
- log('A')
Microtask queue
- empty
Macrotask queue
- empty
Console output
- A
Use Next phase in step mode and name which queue changes each time.
Debugging races
If two callbacks mutate the same DOM node — one from setTimeout, one from fetch().then() — order depends on queue type, not registration order alone.
In radar, avoid long sync work in SSE handlers; defer heavy parsing with queueMicrotask or small chunks if needed.
Teach-back prompt
What runs first after console.log('D') — the Promise callback or the timeout?
Debug a race condition
Lab complete — nice work.