Event Loop
Aboutβ
Event loop is a core concept in JavaScript which enables non-blocking, asynchronous behaviour. It is responsible for managing the execution of code, collecting and processing events, and executing queued tasks. JavaScript operates in a single-threaded environment (one piece of code at a time), but the event loop allows it to handle multiple tasks efficiently, ensuring they are executed in an orderly manner.
Components of Event Loopβ

Call Stackβ
A data structure that stores the execution context of the function calls. It is called a stack because it follows a Last In First Out (LIFO) principle, meaning the last function to be added to the stack is the first one to be removed. Every job enters by pushing a new frame onto the (empty) stack, and exits by emptying the stack.
Web APIs (Background Tasks)β
Provides browser features like setTimeout
, setInterval
, fetch
, DOM events
, and HTTP requests
. These apis handle non-blocking tasks and are not part of the JavaScript runtime.
Callback Queue (Task Queue)β
Holds callback functions that are waiting to be executed after asynchronous operations complete (e.g., setTimeout
, DOM events
, or network requests
). Once the call stack is empty, the event loop pushes the first function from the queue into the call stack for execution.
π¨ "Hey, Iβm done. Let me know when youβre free!" β says your callback
Microtask Queue (High Priority Queue)β
Holds callback functions that are waiting to be executed after the current task is completed. It is processed before the callback queue.
β‘ "Promises first, always!" β Promises jump the queue
Event Loopβ
It continuously checks the call stack and the queue. If the call stack is empty, it pushes the first function from the queue into the call stack for execution.
Notesβ
- Queue operates on FIFO (First In First Out) principle.
- Stack operates on LIFO (Last In First Out) principle.
Types of Tasksβ
- Microtasks: Higher priority asynchronous tasks (Promise.then, queueMicrotask)
- Macrotasks: Lower priority asynchronous tasks (setTimeout, setInterval, DOM events)
How it worksβ
- JavaScript is single-threaded, executing one task at a time via the call stack.
- Synchronous code runs directly in the call stack.
- Asynchronous operations are handled by Web APIs outside the call stack.
- Once an async operation completes, its callback is added to the macrotask queue (aka callback queue)
- Microtasks are added to the microtask queue, which has higher priority than the macrotask queue.
- The event loop monitors the call stack and queues. It constantly checks:
- Is the call stack empty?
- Are there any tasks in the microtask queue?
- Are there any tasks in the callback queue?
- If the call stack is empty, the event loop picks a task from the queue and moves it to the call stack for execution.
Examplesβ
Simple Exampleβ
console.log('Start');
setTimeout(() => {
console.log('macrotask'); // macrotask
}, 0);
Promise.resolve().then(() => {
console.log('microtask'); // microtask
});
console.log('end');
Output:
Start
end
microtask
macrotask
IRL Exampleβ
console.log('Render loading spinner');
// Microtask: Read user preferences from localStorage or IndexedDB (already available)
Promise.resolve().then(() => {
console.log('Apply cached user theme settings');
});
// Macrotask: Fetch user data from backend API (takes time)
setTimeout(() => {
console.log('Fetched user profile from server');
}, 0);
console.log('Initial UI rendered');
Output:
Render loading spinner
Initial UI rendered
Apply cached user theme settings
Fetched user profile from server
Phrases in Node.jsβ
The Node.js event loop runs in multiple phases, and each one handles specific kinds of tasks. Hereβs a simplified breakdown:
- Timers Phase: Executes callbacks scheduled by
setTimeout()
andsetInterval()
if their time has expired. - I/O Callbacks Phase: Runs most I/O-related callbacks, like those from network or file system operations.
- Prepare Phase (internal): Used by Node.js internally to set things up before polling for new events. You typically donβt interact with this directly.
- Poll Phase: Checks for incoming I/O events (like data from a socket or file) and executes their callbacks if ready. If thereβs nothing to do, it may wait.
- Check Phase: Executes callbacks from
setImmediate()
. These are scheduled to run after the poll phase. - Close Callbacks Phase: Runs cleanup callbacks, like when a socket or file is closed (socket.on('close')).
- Microtasks (Between Phases): After each phase, Node.js runs microtasks, such as
- Promise callbacks (.then, .catch, .finally)
- queueMicrotask() -> Microtasks always run before the event loop moves to the next phase.
Think of it like a theme park ride system. Each "phase" is a checkpoint where certain types of passengers (callbacks) are let onto the ride, and VIP microtasks always cut in line before the next round begins.