The terms, at your depth

Every piece of the event loop, explained in the voice you picked up top — switch anytime with the toggle or keys 1 2 3. The pictures these words describe are one click away in every entry.

The call stack

Where your code actually runs. Every function call pushes a frame; every return pops one. JavaScript has ONE call stack — one thing executes at a time, and nothing else (no callback, no promise, no timer) can run until the stack is empty.

Web APIs

Browser-provided machinery that lives OUTSIDE JavaScript: timers, fetch, DOM events. setTimeout does not run your callback — it asks the browser to wait, and the browser later drops the callback into the task queue. This is how a single-threaded language appears to do two things at once.

The callback queue (task queue)

Where setTimeout callbacks, DOM events, and other "macrotasks" wait. The loop takes ONE task per go-around, and only when the stack is empty. A callback in this queue can wait a long time behind a slow script — its timer expiring only means "ready", never "running".

The microtask queue

The priority lane for promise callbacks and queueMicrotask. It drains COMPLETELY after the current script (and after every task), including microtasks queued by other microtasks — before any setTimeout callback gets a chance. That is the whole reason promise callbacks beat timers.

The event loop

Not a thing in your code — a loop the browser runs around it: run a task to completion, drain all microtasks, maybe render, take the next task. It only gets to act when the call stack is empty, which is why blocking the stack freezes everything.

Task vs microtask — the priority rule

setTimeout(cb, 0)  // task
Promise.resolve().then(cb)  // microtask — always wins

The single most-asked interview question on this topic. A microtask queued after a task still runs first, because the loop drains all microtasks between tasks. setTimeout(fn, 0) therefore means "after the current script AND all pending microtasks" — never "now".

setTimeout — why 0ms is not "now"

setTimeout(() => console.log('hi'), 0)

The delay is a MINIMUM, not a schedule. 0ms means "queue the callback as soon as possible" — which is after the current script finishes and after all microtasks drain. Under load, a 0ms callback can be seconds late; a timer never interrupts running code.

Promises & .then

Promise.resolve().then(() => console.log('soon'))

A .then callback NEVER runs synchronously — even on an already-resolved promise it is queued as a microtask. Each .then returns a new promise, so chains run one microtask after another, all before the next task. Promises never touch Web APIs; they live entirely in JS.

async/await

async function main() {
  await Promise.resolve()
  console.log('after')  // runs as a microtask
}

Promises in a costume. Everything before the first await runs synchronously. At await, the function SUSPENDS — it leaves the call stack, and the rest of its body is queued as a microtask when the awaited promise settles. await never blocks the thread; it yields it.

queueMicrotask

queueMicrotask(() => console.log("vip"))

The explicit way to queue a microtask without creating a promise. Same queue, same drain-completely rule as .then callbacks. Use it when you need "after this script, before any task" — and beware: it can starve tasks just like promises can.

Blocking the loop

while (true) {}  // the page is now frozen

A long synchronous job (a heavy loop, a giant JSON.parse) keeps the stack busy, so NOTHING else runs: no timers, no promise callbacks, no clicks, no repaints. This is why "the page froze" and "my timer was late" are the same bug. Break work up, or move it to a Worker.

Microtask starvation

function spin() { queueMicrotask(spin) }  // tasks never run again

Because the microtask queue drains COMPLETELY — including microtasks queued during the drain — a microtask that always queues another one blocks every task forever: timers, clicks, rendering. Promise loops can do this by accident; setTimeout chains cannot (each round is a new task).

The render pipeline (advanced)

Rendering (style → layout → paint) happens BETWEEN tasks, not during them — roughly 60 times a second, after microtasks drain. requestAnimationFrame callbacks run just before that. Change the DOM 1000 times in one task and the user sees only the final state, once.