Call stack
Runs synchronous JavaScript, one function frame at a time.
JavaScript runs one piece of code at a time. The event loop decides when queued callbacks may enter the call stack.
Runs synchronous JavaScript, one function frame at a time.
Handles timers, network requests, user input, and other work outside the stack.
Holds timer, event, and message callbacks ready for a future turn.
Holds Promise reactions and queueMicrotask callbacks.
Run the current script or task to completion. When the stack is empty, drain every microtask. The browser may render, then the event loop can start the next task.
Microtasks go first after the current synchronous work, even if a timer with a delay of 0 is ready.
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
Promise.resolve().then(() => {
console.log("C");
});
console.log("D");
Output
ADCBA prints synchronously.
Its callback will enter the task queue when the timer is eligible. It does not enter the stack yet.
.then schedules a microtask.The Promise is already fulfilled, but its reaction still runs later.
D prints synchronously.The script finishes and the call stack becomes empty.
C prints before another task can begin.
B prints.
The function passed to new Promise runs immediately. A callback passed to .then runs as a microtask.
console.log("start");
new Promise(resolve => {
console.log("executor"); // synchronous
resolve();
}).then(() => {
console.log("then"); // microtask
});
console.log("end");
// start, executor, end, then
await pauses one async functionCode before the first await runs synchronously. The rest of that async function resumes in a microtask after the awaited value settles. The thread itself is not blocked.
async function run() {
console.log("inside 1");
await 0;
console.log("inside 2");
}
console.log("outside 1");
run();
console.log("outside 2");
// outside 1, inside 1, outside 2, inside 2
setTimeout(0) does not mean nowThe delay is the minimum wait before the callback becomes eligible for a task. Existing synchronous work and all queued microtasks still get their turn first. Browsers may also clamp short nested timers.
setTimeout(() => console.log("timer"), 0);
const end = Date.now() + 200;
while (Date.now() < end) {
// blocks the call stack for about 200 ms
}
console.log("loop finished");
// "loop finished" appears before "timer"
The event loop provides concurrency, not parallel execution of JavaScript on the same thread. A long task delays input handlers, timers, rendering, and queued Promise callbacks.
Yield between chunks when the interface needs to stay responsive.
A worker runs JavaScript on another thread and communicates through messages.
The browser drains microtasks before moving on, so continuously adding more can starve tasks and rendering.
.then callback or setTimeout(..., 0)?
The .then callback. It is a microtask, and the event loop drains microtasks before starting the next task.
await block all JavaScript on the page?
No. It pauses that async function. Other synchronous code continues, and the function resumes later in a microtask.
The current task must finish, the microtask queue must drain, and other scheduling delays may apply before the timer callback gets a turn.