JavaScript Event Loop
Call stack, task queue, microtasks, and async execution
The JavaScript Event Loop
Understanding async execution in a single-threaded environment
JavaScript is single-threaded, meaning it can only execute one piece of code at a time. Yet it handles async operations like network requests, timers, and user interactions smoothly. The secret? The Event Loop.
Understanding the event loop is crucial for writing performant JavaScript and predicting the order of async operations. It's also a favorite topic in technical interviews when discussing concepts like Promises, setTimeout, and why certain code executes in unexpected orders.
How the Event Loop Works
The continuous cycle of checking and executing
Event Loop Cycle
Watch how JavaScript coordinates between the call stack, microtasks, and macrotasks
Web APIs (Browser)
JavaScript Engine
Callback Queues
Event loop first checks if the call stack has any frames to execute
Microtasks First
All microtasks run before the next macrotask. Promises always resolve before setTimeout, even with 0ms delay.
One Task at a Time
Only one macrotask is processed per loop iteration. This allows rendering updates between tasks.
The Call Stack
Tracking function execution in LIFO order
Call Stack Visualization
See how functions are pushed and popped from the stack
Call Stack (LIFO)
main() is pushed onto the call stack
Stack Overflow
If functions keep calling other functions without returning, the stack grows until it exceeds
the browser's limit (~10,000-50,000 frames), causing a Maximum call stack size exceeded error.
This commonly happens with infinite recursion.
Microtasks vs Macrotasks
Understanding queue priorities
Queue Priority
Microtasks always run before the next macrotask
// Synchronous
console.log('start');
// Macrotask (Task Queue)
setTimeout(() => {
console.log('timeout');
}, 0);
// Microtask (higher priority)
Promise.resolve().then(() => {
console.log('promise');
});
// Synchronous
console.log('end');Execution Priority
Microtask Queue
HIGH PRIORITYTask Queue (Macrotasks)
LOW PRIORITYCode schedules async tasks: setTimeout and Promise.resolve()
The Golden Rule
Microtasks always run before macrotasks. Even if setTimeout has 0ms delay, Promise.then() callbacks execute first because they're in the microtask queue. This is why the output is: start, end, promise, timeout.
Execution Order Examples
Predict the output of async code
Common Interview Questions
Test your understanding of event loop execution order
Basic Promise vs setTimeout
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');Quick Reference: What Goes Where?
- Promise.then() / .catch() / .finally()
- queueMicrotask()
- MutationObserver callbacks
- await (code after await)
- setTimeout() / setInterval()
- setImmediate() (Node.js)
- I/O operations
- UI rendering events
- requestAnimationFrame (before paint)
Execution Order
Sync code always runs first. Then all microtasks. Then one macrotask. Repeat.
Microtask Starvation
If microtasks keep adding more microtasks, macrotasks (and rendering) get blocked indefinitely.
setTimeout(fn, 0)
Doesn't run immediately! It schedules a macrotask that runs after sync code and all microtasks.
Key Terms to Remember
Master these terms for technical interviews