The Event Loop is the mechanism that allows JavaScript to perform non-blocking operations despite being single-threaded. It continuously monitors the call stack and queues, moving tasks from queues to the call stack when it’s empty.
The Call Stack is a LIFO (Last In, First Out) data structure that keeps track of function calls.
function first() {
console.log("First function");
second();
}
function second() {
console.log("Second function");
}
first();
Output:
First function
Second function
Call Stack Visualization:
Step 1: [first()]
Step 2: [first(), second()]
Step 3: [first()] // second() popped
Step 4: [] // first() popped
Web APIs are browser-provided APIs that handle asynchronous operations like:
setTimeout()
/ setInterval()
console.log("Start");
setTimeout(() => {
console.log("Timeout callback");
}, 0);
console.log("End");
Output:
Start
End
Timeout callback
Explanation: Even with 0ms delay, the setTimeout callback goes to Web APIs, then to the callback queue, and only executes after the call stack is empty.
The Callback Queue stores callbacks from Web APIs waiting to be executed. It follows FIFO (First In, First Out) principle.
console.log("1");
setTimeout(() => console.log("2"), 0);
setTimeout(() => console.log("3"), 0);
console.log("4");
Output:
1
4
2
3
Queue Order: Callbacks are processed in the order they were added to the queue.
The Microtask Queue has higher priority than the Callback Queue. It includes:
.then()
, .catch()
, .finally()
)queueMicrotask()
async/await
operationsconsole.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
Output:
1
4
3
2
Explanation: Promise callback (microtask) executes before setTimeout callback (macrotask).
The Event Loop follows this continuous process:
┌─────────────────┐
│ Call Stack │ ← Executes functions
└─────────────────┘
↑
┌─────────────────┐
│ Microtask │ ← Higher priority
│ Queue │ (Promises, async/await)
└─────────────────┘
↑
┌─────────────────┐
│ Callback │ ← Lower priority
│ Queue │ (setTimeout, setInterval)
└─────────────────┘
↑
┌─────────────────┐
│ Web APIs │ ← Handles async operations
└─────────────────┘
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 1");
});
console.log("End");
Output:
Start
End
Promise 1
Timeout 1
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => {
console.log("3");
return Promise.resolve();
}).then(() => {
console.log("4");
});
setTimeout(() => console.log("5"), 0);
console.log("6");
Output:
1
6
3
4
2
5
console.log("A");
Promise.resolve().then(() => {
console.log("B");
Promise.resolve().then(() => {
console.log("C");
});
});
console.log("D");
Output:
A
D
B
C
console.log("1");
async function asyncFunction() {
console.log("2");
await Promise.resolve();
console.log("3");
}
asyncFunction();
console.log("4");
Output:
1
2
4
3
console.log("1");
setTimeout(() => {
console.log("2");
Promise.resolve().then(() => console.log("3"));
}, 0);
Promise.resolve().then(() => {
console.log("4");
setTimeout(() => console.log("5"), 0);
});
console.log("6");
Output:
1
6
4
2
3
5
// Node.js environment
console.log("1");
setImmediate(() => console.log("2"));
setTimeout(() => console.log("3"), 0);
Promise.resolve().then(() => console.log("4"));
console.log("5");
Output:
1
5
4
3
2
Answer:
setTimeout(() => console.log("1"), 0);
Promise.resolve().then(() => console.log("2"));
console.log("3");
Answer:
3
2
1
Answer:
async function example() {
console.log("A");
await Promise.resolve();
console.log("B"); // This becomes a microtask
}
example();
console.log("C");
Output:
A
C
B
setTimeout(() => {
console.log("1");
setTimeout(() => console.log("2"), 0);
}, 0);
setTimeout(() => console.log("3"), 0);
Output:
1
3
2
function recursiveMicrotask() {
Promise.resolve().then(() => {
console.log("Microtask");
recursiveMicrotask(); // This will starve macrotasks
});
}
setTimeout(() => console.log("Macrotask"), 0);
recursiveMicrotask();
Warning: This creates an infinite loop of microtasks, preventing macrotasks from executing.
console.log("1");
requestAnimationFrame(() => console.log("2"));
setTimeout(() => console.log("3"), 0);
Promise.resolve().then(() => console.log("4"));
console.log("5");
Output:
1
5
4
3
2
// Bad - blocks event loop
function heavyTask() {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
return result;
}
// Good - non-blocking
function heavyTaskAsync() {
return new Promise((resolve) => {
setTimeout(() => {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += i;
}
resolve(result);
}, 0);
});
}
// Prefer this for immediate execution after current task
Promise.resolve().then(() => {
console.log("This runs before next macrotask");
});
// Over this for non-time-critical tasks
setTimeout(() => {
console.log("This runs in next macrotask");
}, 0);
Promise.resolve()
.then(() => {
console.log("1");
return Promise.resolve();
})
.then(() => {
console.log("2");
});
Promise.resolve()
.then(() => {
console.log("3");
});
Output:
1
3
2
The JavaScript Event Loop is crucial for understanding asynchronous JavaScript. Remember:
Understanding these concepts will help you write better asynchronous code and ace JavaScript interviews!
Try to predict the output of these code snippets:
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
setTimeout(() => console.log("D"), 0);
Promise.resolve().then(() => console.log("E"));
console.log("F");
async function async1() {
console.log("1");
await async2();
console.log("2");
}
async function async2() {
console.log("3");
}
console.log("4");
async1();
console.log("5");
Solutions: