The JavaScript Event Loop
Master the JavaScript runtime — interactive event loop simulator, async pattern comparison (callbacks vs Promises vs async/await), and common pitfall debugger with live output.
JavaScript Runtime Architecture
JavaScript is single-threaded — it has one call stack and processes one thing at a time. But it handles asynchronous operations through the event loop, which coordinates between the call stack, Web APIs, and two task queues.
JavaScript Runtime Architecture
Click each component to understand its role. JavaScript is single-threaded but the browser provides concurrency through Web APIs.
LIFO (last-in, first-out) structure. Each function call pushes a frame; returning pops it. JavaScript has ONE call stack — this is why it's single-threaded.
The Event Loop Algorithm
while (true) { // 1. Execute the call stack until empty // 2. Drain ALL microtasks // 3. Render/paint if needed // 4. Pick ONE macrotask → push to call stack // 5. Go to step 1 }
🔑 Golden Rule: After the call stack empties: drain ALL microtasks → pick ONE macrotask → repeat. This is why Promise.then() always runs before setTimeout(cb, 0) — Promises go to the microtask queue which has higher priority.
Event Loop Simulator
Step through real code execution and watch items move between the call stack, microtask queue, and macrotask queue. This is the best way to build an intuition for execution order.
Event Loop Simulator
Step through code execution and watch items move between the call stack, microtask queue, and macrotask queue.
console.log("1: Start");
setTimeout(() => {
console.log("4: setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("3: Promise");
});
console.log("2: End");Call Stack
Micro Queue
Macro Queue
Async Patterns & The Event Loop
JavaScript evolved from callbacks to Promises to async/await — but all three patterns interact with the event loop differently. Understanding which queue each pattern uses explains why execution order can be surprising.
Async Patterns & The Event Loop
JavaScript evolved from callback hell to clean async/await — but all three patterns use the event loop differently.
Callbacks — ES5 (2009)
loadUser(1, function(user) {
loadPosts(user.id, function(posts) {
loadComments(posts[0].id, function(comments) {
console.log(comments);
// "Callback Hell" — deeply nested
});
});
});Event Loop Flow
✅ Pros
• Simple concept
• Works everywhere
❌ Cons
• Callback hell / pyramid of doom
• Error handling is manual
• Hard to compose
• Inversion of control — you trust the caller to call your callback
| Pattern | Queue Used | Priority |
|---|---|---|
| Callbacks (setTimeout) | Macrotask queue | Low — runs after microtasks |
| Promise.then() | Microtask queue | High — runs before macrotasks |
| async/await | Microtask queue (Promises) | High — same as Promises |
Common Pitfalls
These bugs trip up every JavaScript developer — from setTimeout(fn, 0) not being instant to microtasks starving the main thread. Each one is rooted in a misunderstanding of the event loop.
Common Event Loop Pitfalls
These bugs trip up every JavaScript developer. Understanding the event loop is the key to avoiding them.
Code
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");Actual Output
Why?
setTimeout(fn, 0) doesn't mean "run immediately." It means "add to the macrotask queue as soon as possible." Microtasks (Promise.then) always run before macrotasks. So: A (sync) → D (sync) → C (microtask) → B (macrotask).