One of the most frequently asked JavaScript interview questions is:
How does the JavaScript Event Loop work?
This question appears in:
- Frontend Interviews
- React Interviews
- Senior JavaScript Interviews
- Full Stack Interviews
Interviewers often ask:
- Is JavaScript single-threaded?
- How can JavaScript perform asynchronous operations?
- What is the Event Loop?
- What are Microtasks and Macrotasks?
- Why do Promises execute before
setTimeout?
To answer these questions, we need to understand how JavaScript executes code behind the scenes.
1. Is JavaScript Single-Threaded?#
Yes.
JavaScript executes code on a single thread.
This means:
One Task At A Time
Example:
console.log("A");
console.log("B");
console.log("C");
Output:
A
B
C
JavaScript cannot execute:
A and B simultaneously
on the same thread.
2. The Problem of Blocking Operations#
Consider:
console.log("Start");
setTimeout(() => {
console.log("Timer");
}, 5000);
console.log("End");
Output:
Start
End
Timer
Question:
If JavaScript is single-threaded, why doesn't it wait 5 seconds?
The answer lies in:
Event Loop
3. What is the Event Loop?#
The Event Loop is a mechanism that allows JavaScript to perform asynchronous operations without blocking the main thread.
It coordinates:
- Call Stack
- Web APIs
- Callback Queue
- Microtask Queue
Together they enable asynchronous behavior.
4. Key Components: Call Stack & Web APIs#
The Event Loop relies on four major components:
Call Stack
Web APIs
Callback Queue
Event Loop
Modern JavaScript also includes:
Microtask Queue
Call Stack#
The Call Stack keeps track of currently executing functions.
Example:
function greet() {
console.log("Hello");
}
greet();
Execution:
Call Stack
greet()
↓
console.log()
After execution:
Call Stack Empty
Stack Example#
function one() {
two();
}
function two() {
three();
}
function three() {
console.log("Done");
}
one();
Call Stack:
one()
↓
two()
↓
three()
↓
console.log()
Then functions are removed one by one.
Web APIs#
Browsers provide APIs outside JavaScript itself.
Examples:
setTimeout
fetch
DOM Events
addEventListener
Geolocation
These APIs are handled by the browser.
Not by the JavaScript engine.
Example#
setTimeout(() => {
console.log("Timer");
}, 3000);
Execution:
Call Stack
↓
Browser Web API
↓
Wait 3 Seconds
↓
Queue Callback
The timer does not sit on the Call Stack.
5. Callback Queue (Macrotasks)#
After an asynchronous task completes:
setTimeout(...)
its callback is placed into the:
Callback Queue
Example:
setTimeout(() => {
console.log("Timer");
}, 0);
After timer completion:
Callback Queue
console.log("Timer")
The callback waits until the Call Stack becomes empty.
Event Loop's Job#
The Event Loop continuously checks:
Is Call Stack Empty?
If yes:
Move Task From Queue
To Call Stack
This process repeats forever.
Visual Representation#
Call Stack
↑
|
Event Loop
|
↓
Callback Queue
First Interview Question#
What is the output?
console.log("Start");
setTimeout(() => {
console.log("Timer");
}, 0);
console.log("End");
Answer#
Output:
Start
End
Timer
Why?
Even:
setTimeout(..., 0)
does not execute immediately.
The callback must first enter the queue and wait for the Call Stack to become empty.
6. Promises & the Microtask Queue#
Promises do not use the Callback Queue.
They use:
Microtask Queue
Example:
Promise.resolve().then(() => {
console.log("Promise");
});
Two Important Queues#
Microtask Queue
Macrotask Queue
Examples:
Microtasks
Promise.then()
catch()
finally()
queueMicrotask()
MutationObserver
Macrotasks
setTimeout()
setInterval()
DOM Events
MessageChannel
7. Queue Priority: Microtasks vs. Macrotasks#
The Event Loop always processes:
Microtasks First
before:
Macrotasks
This is one of the most important interview concepts.
Promise vs setTimeout#
What is the output?
console.log("Start");
setTimeout(() => {
console.log("Timer");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Answer#
Output:
Start
End
Promise
Timer
Why?
Execution:
Start
End
Then:
Microtask Queue
↓
Promise
Then:
Macrotask Queue
↓
Timer
Microtasks always run first.
8. Async/Await and the Event Loop#
Consider:
async function demo() {
console.log("A");
await Promise.resolve();
console.log("B");
}
demo();
console.log("C");
Answer#
Output:
A
C
B
Why?
await creates a Promise continuation.
The remaining code:
console.log("B");
is placed into the:
Microtask Queue
9. Event Loop Execution Order#
The Event Loop follows:
1. Execute Sync Code
2. Empty Microtask Queue
3. Execute One Macrotask
4. Empty Microtask Queue Again
5. Repeat
This order is extremely important.
Advanced Interview Question#
What is the output?
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => {
console.log("3");
});
console.log("4");
Answer#
Output:
1
4
3
2
Explanation:
1 → Sync
4 → Sync
3 → Microtask
2 → Macrotask
Advanced Interview Question#
What is the output?
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve()
.then(() => {
console.log("3");
return Promise.resolve();
})
.then(() => {
console.log("4");
});
console.log("5");
Answer#
Output:
1
5
3
4
2
Why?
All microtasks finish before any macrotask begins.
10. Browser Rendering and the Event Loop#
Browsers typically render:
After Current Task Completes
Example:
button.style.color = "red";
while (true) {}
The UI may never update because the thread is blocked.
Why Long Tasks Are Bad#
Example:
while (true) {
// Infinite loop
}
Result:
Blocked Event Loop
Effects:
- Frozen UI
- Delayed timers
- Unresponsive application
11. Common Interview Q&A#
Does setTimeout(0) Execute Immediately?#
No.
It enters the Macrotask Queue and waits.
Why Do Promises Execute Before Timers?#
Because Promise callbacks are Microtasks.
Microtasks have higher priority.
Is JavaScript Multithreaded?#
The JavaScript execution thread is single-threaded.
However, browsers provide asynchronous capabilities through Web APIs.
Does Await Block JavaScript?#
No.
It pauses only the current async function.
The Event Loop continues running.
What Queue Does Promise.then() Use?#
Microtask Queue
What Queue Does setTimeout() Use?#
Macrotask Queue
Senior-Level Interview Answer#
JavaScript is single-threaded and executes code using a Call Stack. Asynchronous operations such as timers, network requests, and DOM events are handled by browser Web APIs. Once completed, their callbacks are placed into task queues. The Event Loop continuously checks whether the Call Stack is empty and then moves queued tasks onto the stack for execution. Promise callbacks and async/await continuations are scheduled as Microtasks, while setTimeout callbacks are scheduled as Macrotasks. The Event Loop always processes all Microtasks before executing the next Macrotask, which is why Promise callbacks execute before timers.
Common Interview Mistakes#
❌ "JavaScript is asynchronous."
Correct:
JavaScript is synchronous and single-threaded.
The Event Loop enables asynchronous behavior.
❌ "setTimeout(0) executes immediately."
Correct:
It waits for the Call Stack to become empty.
❌ "Promises use the Callback Queue."
Correct:
Promises use the Microtask Queue.
❌ "Await blocks JavaScript."
Correct:
Await pauses only the current async function.
Event Loop Summary#
| Component | Responsibility |
|---|---|
| Call Stack | Executes functions |
| Web APIs | Handle async operations |
| Microtask Queue | Promise callbacks |
| Macrotask Queue | Timers and events |
| Event Loop | Coordinates execution |
Key Takeaways#
- Single-Threaded JS: The JS runtime handles one task at a time using a Call Stack, offloading async tasks to Web APIs.
- Microtask Priority: The Microtask Queue (Promises) is completely flushed before the next Macrotask Queue item (timers) executes.
- Event Loop: Monitors the Call Stack and moves callbacks from queues onto the stack once it is empty.
- Browser Rendering: Updates generally occur between event ticks once the stack clears, blocked by long-running loops.