Reason about callback ordering
Predicting console output when timers and Promise callbacks are mixed.
JS Puzzles · Const
The JavaScript event loop processes macrotasks one at a time, then drains all pending microtasks before rendering and moving to the next macrotask. This ordering governs scripts, timers, user events, and Promise callbacks.
The event loop repeatedly takes the oldest macrotask, runs it to completion, drains every queued microtask, and then allows rendering before continuing. Timers, scripts, and user events supply macrotasks, while promise handlers and explicit microtasks use the microtask queue; this ordering explains why asynchronous callbacks do not interrupt currently running JavaScript.
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);The script task prints 1 and 4, then the promise reaction runs as a microtask, and the timer callback runs as a later macrotask.
Predicting console output when timers and Promise callbacks are mixed.
Breaking work into scheduled pieces so the browser can handle other tasks between chunks.
Choosing between a timer and a microtask for follow-up work.
The loop removes the oldest macrotask and executes it. It then drains the microtask queue completely, renders if needed, and waits for the next macrotask when none is available.
Follow-up: What happens if a microtask queues another microtask? The newly queued microtask is processed in the same drain, before the next macrotask.
A timer callback is a macrotask, while a promise reaction is a microtask. Therefore, when both are scheduled by the same task, the promise reaction runs first after the current task ends.
Follow-up: Would zero delay make the timer run first? No; zero delay schedules a later macrotask and does not bypass the microtask checkpoint.
The current JavaScript task runs synchronously from start to finish. The event loop does not pause it to execute a callback; callbacks are queued and considered only after the current task completes.
Follow-up: How can asynchronous code still delay input? A continuously growing microtask queue can prevent the loop from reaching later tasks, including input handling.
macrotask queuemicrotask queuesetTimeoutPromise handlersrendering