Understanding JavaScript Event Loop with Example (setTimeout vs Promise)?
Meta Description:
Confused why a Promise.then() runs before setTimeout() with a 0ms delay? Learn how JavaScript’s event loop, microtasks, and macrotasks control the execution order, with real code examples.
🔍 Introduction
JavaScript is single-threaded, but thanks to the event loop, it can handle asynchronous operations with surprising precision. One common interview or real-world developer question is:
Why does a
Promise.then()run before asetTimeout()with 0ms delay?
Let’s break this down using a simple snippet that’s both deceivingly small and incredibly educational.
🧪 The Code
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
📋 What’s the Expected Output?
Many developers expect this:
1
2
3
4
But the actual output is:
1
4
3
2
Why?
🧠 The JavaScript Execution Model
To understand this, we need to look at:
- The Call Stack
- Web APIs (like
setTimeout) - The Microtask Queue (for Promises)
- The Macrotask Queue (for
setTimeout,setInterval, etc.) - The Event Loop, which coordinates everything
🧵 Line-by-Line Breakdown
console.log('1');
This is synchronous. It executes immediately and prints 1.
✅ Output: 1
setTimeout(() => console.log('2'), 0);
setTimeoutis handled by the browser, not the JavaScript engine.- The callback is scheduled in the macrotask queue.
- The delay is 0ms, but that only means “run as soon as the stack and microtasks are clear.”
⏳ Deferred to: Macrotask Queue
Promise.resolve().then(() => console.log('3'));
- Promises use the microtask queue.
- Microtasks always run before macrotasks, even if both are ready at the same time.
✅ Scheduled in: Microtask Queue
console.log('4');
Another synchronous log. It runs immediately.
✅ Output: 4
🔄 Now What Happens?
Once all synchronous code is done, the JavaScript event loop kicks in:
- Microtasks (like
.then()) are executed first. → Logs3 - Then the first macrotask (like
setTimeout) is executed. → Logs2
🖨 Final Output
1
4
3
2
🧵 Visual Summary of Execution Order
[Call Stack]
1: console.log('1') → prints 1
2: console.log('4') → prints 4
[Microtask Queue]
- Promise.then(...) → prints 3
[Macrotask Queue]
- setTimeout(...) → prints 2
✅ Why Does This Matter?
Understanding JavaScript’s event loop is key to mastering:
- Asynchronous behavior
- Performance optimizations
- Writing predictable, bug-free code
It also clears up one of the most confusing parts of JS: “Why does a Promise run before a setTimeout?”
🧾 TL;DR
| Code | Type | Executes When | Output |
|---|---|---|---|
console.log('1') | Synchronous | Immediately | 1 |
setTimeout(..., 0) | Macrotask | After microtasks | 2 |
Promise.resolve().then(...) | Microtask | After sync code | 3 |
console.log('4') | Synchronous | Immediately | 4 |
✅ Final Output:
1
4
3
2
✍️ Conclusion
The key takeaway: Microtasks (Promises) are always executed before macrotasks (setTimeout), no matter the delay. Knowing how the event loop, microtask queue, and macrotask queue interact is fundamental to mastering JavaScript asynchronous programming.
