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:

StructureHoldsProcessed when
Call stackCurrently running functionImmediately (LIFO)
Microtask queuePromise.then, queueMicrotaskStack empty — all microtasks, repeatedly
Macrotask queuesetTimeout, I/O, UI eventsOne per loop turn after microtasks

Drain algorithm (simplified)

  1. Run sync code until stack is empty
  2. Run every microtask until micro queue empty
  3. Run one macrotask
  4. 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

Lesson 1.2 check

1. Microtasks run…