To write highly performant JavaScript, you must understand how its runtime executes code. Although JavaScript is a single-threaded language, it operates asynchronously through the Event Loop. Let's break down its architectural parts.
1. The Call Stack
The call stack is a LIFO (Last In, First Out) stack that tracks the currently executing function. When a function is called, it is pushed onto the stack; when it returns, it is popped off.
2. Web APIs / Node C++ APIs
Since the runtime thread can't block, async APIs (like setTimeout, fetch requests, or DOM events) are handed off to the browser's C++ Web APIs (or Node.js's libuv thread pool) to execute in the background.
3. Task Queue (Macrotasks) & Microtask Queue
When an async operation completes, its callback is moved into a queue waiting for execution:
- Microtask Queue: Holds callbacks from
Promises(.then,awaitreactions) andMutationObserver. - Callback Queue (Macrotasks): Holds callbacks from
setTimeout,setInterval, and DOM events.
Critical Execution Rule: The Event Loop will prioritize the Microtask Queue over the Macrotask Queue. It will empty the *entire* microtask queue before moving on to execute a single macrotask callback.
Code Challenge: What is the Output?
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
Answer:
Start(Synchronous call stack)End(Synchronous call stack)Promise(Microtask runs before Macrotask)Timeout(Macrotask runs)