Introduction
JavaScript runs on a single thread, yet it handles thousands of concurrent operations—network requests, timers, user interactions. The secret is the event loop, a clever mechanism that orchestrates async execution. Understanding it is essential for writing efficient, bug-free async code.
Key Concepts
Call Stack: LIFO structure tracking function execution. Only one thing runs at a time.
Event Loop: Continuously checks if the call stack is empty, then processes queued tasks.
Non-blocking: Operations that don't prevent other code from running.
Real World Context
When a fetch request takes 500ms, your UI doesn't freeze. When a click handler runs, other events queue up. Understanding this prevents blocking bugs and helps debug timing issues in complex applications.
Deep Dive
The Call Stack
javascriptfunction multiply(a, b) { return a * b; } function square(n) { return multiply(n, n); } function printSquare(n) { const result = square(n); console.log(result); } printSquare(4); // Stack: [printSquare] -> [square, printSquare] // -> [multiply, square, printSquare] -> pops down
How Async Works
javascriptconsole.log('Start'); // 1. Runs immediately setTimeout(() => { console.log('Timeout'); // 4. Runs after stack is empty }, 0); console.log('End'); // 2. Runs immediately // Output: Start, End, Timeout // Even 0ms timeout goes to queue!
The Mechanism
- Call Stack: Executes synchronous code
- Web APIs: Handle async operations (timers, fetch, events)
- Task Queue: Stores callbacks ready to run
- Event Loop: Moves tasks from queue to stack when empty
javascript// Visualization: // // [Call Stack] [Web APIs] [Task Queue] // main() --> setTimeout() --> callback() // --> fetch() --> .then() // --> onClick --> handler() // // Event Loop: Stack empty? Move from Queue to Stack
Why 0ms Timeout Isn't Immediate
javascriptsetTimeout(() => console.log('timeout'), 0); console.log('sync'); // Output: sync, timeout // setTimeout ALWAYS goes through the queue // minimum delay is ~4ms in most browsers
Common Pitfalls
- Assuming setTimeout(0) is immediate: It still goes through the queue.
- Blocking with synchronous loops: Freezes the entire page.
- Expecting consistent timing: Busy stack delays queue processing.
Best Practices
- Keep synchronous work short: Long tasks block everything.
- Use async/await for clarity: Easier to reason about than callbacks.
- Profile with DevTools: Performance tab shows long tasks.
- Break up heavy computation: Use
requestIdleCallbackor workers.
Summary
JavaScript is single-threaded with one call stack. Async operations use Web APIs and queues. The event loop moves queued callbacks to the stack when empty. Even 0ms timeouts go through the queue. Long synchronous code blocks everything.
Code Examples
function multiply(a, b) {
return a * b;
}
function square(n) {
return multiply(n, n);
}
function printSquare(n) {
const result = square(n);
console.log(result);
}
printSquare(4);
// Stack: [printSquare] -> [square, printSquare]
// -> [multiply, square, printSquare] -> pops downconsole.log('Start'); // 1. Runs immediately
setTimeout(() => {
console.log('Timeout'); // 4. Runs after stack is empty
}, 0);
console.log('End'); // 2. Runs immediately
// Output: Start, End, Timeout
// Even 0ms timeout goes to queue!