▶
All 15

Glossary

The runtime vocabulary, one page. Each term comes with the distinction that actually matters and a case where you can watch it in action.

Event loop

The host mechanism that repeatedly picks one task, runs it to completion, drains the microtask queue, and (in browsers) may then update the rendering. It is scheduling logic in the host, not a feature of the JavaScript language itself.

Easily confused: Not a thread and not a queue — it is the loop that services the queues. JavaScript stays single-threaded; concurrency lives in the host around it.

How the Event Loop Runs → · How It All Runs Together →

Call stack

The single stack of frames tracking which function is currently executing. Synchronous code runs until this stack is empty; only then can the event loop deliver anything from a queue.

Easily confused: A stack (LIFO), not a queue: the newest frame is always the one running. "Stack empty" is the precondition for every queued callback.

How the Event Loop Runs →

Task ("macrotask")

One unit of work the event loop runs per iteration: executing a script, a timer callback, an event handler. The HTML spec calls these tasks; "macrotask" is community shorthand that appears in no specification.

Easily confused: One task per loop iteration — unlike microtasks, which drain completely. Browsers keep several task queues (timers, user interaction, networking) and may choose between them.

How Timers Really Run → · How Promises Run →

Microtask

A callback in the microtask queue: promise reactions, await continuations, queueMicrotask, MutationObserver. Drained completely at the checkpoint after the current task — including microtasks queued during the drain.

Easily confused: Beats every waiting task, but cannot interrupt running code. An endless microtask chain starves tasks and rendering alike.

How Promises Run → · How Promise Chains Run →

Microtask checkpoint

The moment the microtask queue is drained: whenever the call stack empties after running a task or callback. This is also when the host decides which rejected promises count as unhandled.

Easily confused: A point in time, not a place. "When the stack empties" is the answer to most "when does my .then run?" questions.

How Rejections Run →

await continuation

The rest of an async function after an await, packaged up when the function suspends. It queues as an ordinary microtask once the awaited promise settles — FIFO with everything else in that queue.

Easily confused: While the promise is pending, the continuation is parked on it — NOT waiting in any queue. Suspended and queued are different states.

How await Runs → · How fetch Runs →

Unhandled rejection

A promise that is rejected and still has no rejection handler when the host checks after the microtask checkpoint. Browsers fire the unhandledrejection event; modern Node exits the process by default.

Easily confused: Decided by the host at the checkpoint, not at the throw. Attaching a .catch in the same turn keeps a rejection out of unhandled territory entirely.

How Rejections Run →

process.nextTick (Node)

A Node-only queue drained before promise microtasks, between every phase of its event loop. Node documents it as legacy and recommends queueMicrotask for ordinary deferral.

Easily confused: Not a microtask — a separate, higher-priority queue. And the "before promises" ordering is a CommonJS observation: top-level ESM code already runs inside a microtask, which flips it.

How Node Runs It →

Module job (ESM evaluation)

ES modules are evaluated as jobs — meaning top-level .mjs code runs inside a microtask context, unlike a classic script or CommonJS module body, which run as a plain task.

Easily confused: The reason the same Node file can print a different order as .cjs and .mjs. The invocation context is part of the semantics.

How Node Runs It →

Rendering step

The browser phase that may follow the microtask checkpoint: run requestAnimationFrame callbacks, recompute style, run layout, paint. "May" — the browser renders when a frame is due, not every loop iteration.

Easily confused: Not a queue the loop polls; a phase it may enter. Everything JavaScript does sits between paints, which is why a long task or microtask chain drops frames.

How Rendering Fits In →

"Web APIs"

The teaching label for host machinery outside JavaScript: timer threads, the network stack, I/O. Work handed to it proceeds off the main thread; finished callbacks come back through the queues.

Easily confused: A diagram label, not a spec term. The point it makes is real, though: the waiting happens outside JavaScript, which is why the thread stays free.

How Timers Really Run → · How fetch Runs →

queueMicrotask()

The direct API for enqueuing a callback on the microtask queue — the exact queue promise reactions use, with strict FIFO between them. The standard, portable replacement for the old Promise.resolve().then(fn) idiom.

Easily confused: Not a faster setTimeout: a microtask runs at the checkpoint inside the current turn, before any task and before rendering. A timer callback is a task, one turn (and possibly one paint) later.

How queueMicrotask Runs →

setImmediate (Node)

Node-only: schedules a callback for the check phase of the event loop — the phase right after poll. Inside an I/O callback it is guaranteed to run before any setTimeout(fn, 0) created there.

Easily confused: Despite the name, nothing runs immediately: it means "this loop iteration, check phase". At top level its order against setTimeout(0) is a genuine race; inside an I/O callback it is deterministic.

How setImmediate Runs →

Event loop phases (Node)

One iteration of Node's loop visits fixed phases in order: timers → pending callbacks → poll (deliver I/O) → check (setImmediate) → close callbacks. The nextTick queue and then the microtask queue are drained after every callback returns — not merely at phase boundaries.

Easily confused: Phases are Node/libuv architecture, not JavaScript semantics — the browser loop has tasks and checkpoints but no phase cycle. Ordering between setTimeout, setImmediate and I/O is a phase question; ordering against nextTick and promises is a per-callback drain question.

How setImmediate Runs → · How Node Runs It →

Starvation

When a greedily-drained queue never empties, everything scheduled behind it never runs. A microtask (or nextTick) callback that always queues a successor blocks every task, every timer, all I/O and all rendering — indefinitely.

Easily confused: Not a bug in the loop — it is the drain-until-empty rule working as specified. The fix is structural: chunk long work at task boundaries (setImmediate, timers), never inside the microtask queue.

How queueMicrotask Runs → · How Node Runs It → · How Rendering Fits In →

setTimeout clamping

Hosts enforce a minimum delay: Node clamps 0 to 1ms; browsers clamp nested timers (depth > 5) to 4ms and throttle background tabs far more aggressively. The delay you pass is a floor, never a schedule.

Easily confused: Clamping is why setTimeout(fn, 0) is not "run next" — and in Node, why its race against setImmediate at top level depends on process startup time.

How Timers Really Run → · How setImmediate Runs →

Temporal dead zone (TDZ)

The stretch between entering a scope and reaching a let/const declaration, during which the binding exists but reads throw a ReferenceError. The engine registered the name during setup; it just refuses access until the declaration line runs.

Easily confused: Not "let is not hoisted" — the binding IS created early, which is exactly why the error is a ReferenceError and not undefined. var's undefined is initialization-at-setup; TDZ is registration-without-initialization.

How the TDZ Works →

Fail-fast (Promise.all)

Promise.all rejects the moment any input rejects — but rejection only settles the combined promise. The other input promises keep running to completion; their work is not cancelled, merely unobserved.

Easily confused: Settling is not stopping: JavaScript promises are not cancellable. If abandoning the losers matters (network requests, timers), you need AbortController, not a different combinator.

How Promise.all Runs →

AbortController / AbortSignal

The standard cooperative cancellation primitive: create a controller, hand controller.signal to an API that accepts one, then call controller.abort(). What abort does next is up to that API — fetch rejects its promise with signal.reason (an AbortError DOMException unless you pass your own reason), while addEventListener(..., { signal }) simply removes the listener and rejects nothing.

Easily confused: Cooperative, not universal: it only does something where an API opted in, and it is not the only way to cancel (clearTimeout exists, and many libraries ship their own). It matters here because promises themselves are not cancellable — Promise.all rejecting fail-fast does not stop the losing requests; only a signal the underlying API honours can.

How fetch Runs → · How Promise.all Runs →

Thenable

Any object with a .then method. Promise resolution treats it like a promise: resolving to a thenable defers to its then(), which costs an extra trip through the microtask queue — the detail behind most "why is this one tick late?" chain puzzles.

Easily confused: Duck typing, by spec: await and Promise.resolve unwrap thenables whether or not they are real Promises. A malicious or slow .then is invoked with the same trust as a native one.

How Promise Chains Run →

Sources: HTML Standard — Event loops · ECMAScript — Jobs · MDN — Microtask guide . Last reviewed 4 August 2026.