Event Loop Deep Dive. Microtasks vs Macrotasks for Interviews
Event Loop is a mechanism in JavaScript that manages asynchronous tasks and event queues. It allows JavaScript to work in a single-threaded model, processing asynchronous operations without blocking the main execution thread.
How Does Event Loop Work?
Event Loop is an infinite loop that executes event handlers. During its operation, the browser distributes tasks into two main queues:
- Microtasks — tasks such as promises,
queueMicrotask(),MutationObserver. - Macrotasks — tasks such as
setTimeout, events, timers,fetchrequests.
Main Execution Order
- First, all synchronous tasks execute from Call Stack.
- Then all tasks from microtask queue execute. All microtasks will execute before next macrotask is taken. Microtasks block Event Loop until all are executed.
- After executing all microtasks queue is cleared.
- Next, one macrotask is taken from macrotask queue and executed. Macrotasks like
setTimeoutwait until all synchronous tasks and microtasks are completed. - After executing macrotask checks if page needs to be redrawn. If necessary, browser performs rendering.
- Repeat cycle. Event Loop continues processing tasks until all operations are completed.
Data Structures
-
Call Stack (LIFO — Last In, First Out):
- Call stack contains all functions executing at current moment.
- When function is called, it's added to stack. If another function is called inside, it's also added to stack.
- After function executes, it's removed from stack.
-
Web API:
- Asynchronous operations like
setTimeout, event handlers,fetchrequests go to Web API. - After asynchronous task completes, it doesn't immediately go to Call Stack. Instead it's placed in Callback Queue.
- Asynchronous operations like
-
Callback Queue (FIFO — First In, First Out):
- Stores functions that should execute after call stack becomes empty.
- When call stack clears, Event Loop moves tasks from Callback Queue to Call Stack and executes them.
Microtasks vs. Macrotasks
-
Macrotasks:
- Example:
setTimeout, events (onClick,onChange),fetchrequests. - These tasks are processed in queue order, one after another, after all synchronous tasks and microtasks execute.
- Example:
-
Microtasks:
- Example: promises,
queueMicrotask(),MutationObserver. - Microtasks execute immediately after synchronous operations and before macrotasks.
- All microtasks from queue execute before next macrotasks.
- Example: promises,
Example
console.log("First message");
setTimeout(() => {
console.log("Second message");
}, 0);
Promise.resolve().then(() => console.log("Third message"));
console.log("Fourth message");
What Happens:
- First message outputs first because it's synchronous operation.
setTimeoutplaces its task in macrotask queue, but won't execute until all microtasks are processed.Promise.resolve().then(...)places task in microtask queue and will execute before next macrotask.- Fourth message outputs next as it's synchronous code.
- After synchronous code completes, microtask executes (Third message outputs).
- Second message outputs last as it's macrotask and executes only after all microtasks.
Important Note:
Microtasks have priority over macrotasks. So even if you use setTimeout with delay 0, function will execute after all microtasks.