- Home
- /
- Tutorials
- /
- JavaScript Tutorial
- /
- JavaScript Event Loop
JavaScript Asynchronous
JavaScript Event Loop
The event loop decides what runs next when the call stack is empty.
This is the mechanism behind everything in this section.
It also explains one of the most common interview questions in JavaScript.
Example
Example
javascript
console.log("first");
setTimeout(function () {
console.log("fourth");
}, 0);
Promise.resolve().then(function () {
console.log("third");
});
console.log("second");The output is first, second, third, fourth.
The promise runs before the timer, even though the timer was scheduled first.
The Parts Involved
The call stack runs your code, one thing at a time.
The task queue holds timer and event callbacks.
The microtask queue holds promise callbacks, and always goes first.
Syntax
Syntax
javascript
// stack empties -> all microtasks -> one task -> repeatMicrotasks are drained completely before the next task.
Synchronous Code Goes First
Nothing queued can run until the current code has finished.
Example
Example
javascript
setTimeout(function () {
console.log("last");
}, 0);
for (let i = 0; i < 3; i++) {
console.log("loop " + i);
}The whole loop finishes before the timer callback gets a turn.
Microtasks Beat Tasks
A promise callback is a microtask.
Every microtask runs before the next timer callback, whatever the delay.
Example
Example
javascript
setTimeout(function () {
console.log("timer");
}, 0);
Promise.resolve().then(function () {
console.log("promise");
});The output is promise then timer.
Microtasks Can Queue More
A microtask that queues another still runs before any task.
The queue is drained completely, not one at a time.
Example
Example
javascript
setTimeout(function () {
console.log("timer");
}, 0);
Promise.resolve()
.then(function () {
console.log("promise one");
return Promise.resolve();
})
.then(function () {
console.log("promise two");
});Both promise steps run before the timer.
Why a Long Loop Freezes the Page
Nothing in either queue can run while the stack is busy.
That includes clicks, rendering and timers.
Example
Example
javascript
const started = Date.now();
setTimeout(function () {
console.log("the timer was delayed by the loop");
}, 0);
while (Date.now() - started < 30) {
// deliberately blocking
}
console.log("loop finished");The timer could not fire until the blocking loop released the thread.
await Uses Microtasks Too
Code after an await is resumed as a microtask.
Example
Example
javascript
async function run() {
await Promise.resolve();
console.log("after await");
}
setTimeout(function () {
console.log("timer");
}, 0);
run();
console.log("sync");The output is sync, after await, timer.
Complete Example
Complete Example
html
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Event Loop</title>
</head>
<body>
<h1>The Event Loop</h1>
<p id="out"></p>
<script>
const order = [];
setTimeout(function () {
order.push("timer");
document.getElementById("out").textContent = order.join(" → ");
}, 0);
Promise.resolve().then(function () {
order.push("promise");
});
order.push("synchronous");
</script>
</body>
</html>Try It Yourself
Run the above example in the Try It Editor.
Try predicting first:
Write down the order you expect before running it, then check.
Important Points
- The call stack runs code one thing at a time.
- Queued work waits until the stack is empty.
- Microtasks are promise callbacks and run first.
- Tasks are timer and event callbacks.
- All microtasks are drained before the next task.
Conclusion
The event loop is why asynchronous JavaScript behaves the way it does.
Microtasks before tasks explains almost every ordering puzzle you will meet.
It is also one of the most reliably asked interview questions.
