1 . What Does “Single-Threaded” Mean?
2 . The Call Stack: JavaScript’s Execution Backbone
3 . Web APIs: Browser-Level Asynchrony
4 . Task Queues and Callback Queues
5 . Microtasks vs Macrotasks
6 . Understanding setTimeout, Promises, and I/O
7 . The Event Loop: Step-by-Step Flow
9 . Visualizing with Examples
11 . Best Practices for Working with the Event Loop
12 . Tools for Debugging Asynchronous Behavior
13 . Evolution: How the Event Loop Works in Node.js
Conclusion
JavaScript engines like V8 (used in Chrome and Node.js) execute JavaScript code on a single thread. This means:
But JavaScript doesn’t operate in a vacuum. It interacts with the host environment (browser or Node.js), which has its own threads and APIs to provide async capabilities.
The call stack is where functions are executed and tracked.
Example:
function greet() {
console.log("Hello");
}
function start() {
greet();
}
start();
Call stack sequence:
Everything runs in order. No parallel execution.
Here’s the twist.
When we call something like setTimeout, fetch, or DOM events, JavaScript itself doesn’t handle the delay. The browser or Node’s environment does.
For example:
setTimeout(() => {
console.log("Timer done");
}, 1000);
The timer is handled by the browser. Once it's done, the callback is queued to be executed by the event loop.
JavaScript says, “Hey browser, set this timeout. When it’s done, notify me via the queue.”
Once async tasks are done (e.g., HTTP response received), they are placed in a queue. JavaScript picks tasks from this queue when the call stack is empty.
These queues include:
Microtasks include:
Macrotasks include:
Execution priority:
Example:
console.log("Start");
setTimeout(() => {
console.log("Macrotask");
}, 0);
Promise.resolve().then(() => {
console.log("Microtask");
});
console.log("End");
Output:
Start → End → Microtask → Macrotask
Although setTimeout(..., 0) seems instant, it’s not. It gets queued after the current task and all microtasks finish.
Example:
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("sync");
Output:
sync → promise → timeout
Even though setTimeout has 0ms, Promise resolves first due to microtask priority.
Here’s a simplified version of what happens:
1 . Execute global script → build call stack 2 . Finish stack execution 3 . Check microtask queue → execute all 4 . Check macrotask queue → execute next
Repeat
This endless loop is called the “Event Loop.”
It ensures that JS doesn’t block and handles asynchronous behavior predictably.
8 . Asynchronous Myths in JavaScript
❌ JavaScript is asynchronous ✅ JavaScript is synchronous with asynchronous capabilities provided by its runtime ❌ setTimeout executes after the given time ✅ setTimeout only queues after that time—actual execution depends on when the event loop is free
Let’s look at a complex sequence:
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
Promise.resolve().then(() => {
console.log("C");
});
console.log("D");
Output:
A → D → C → B
Reason:
10 . Real-World Scenarios & Pitfalls
Example: DOM Manipulation timing
document.querySelector("button").addEventListener("click", () => {
setTimeout(() => {
console.log("Clicked");
}, 0);
});
Pitfall: Assuming setTimeout(..., 0) will run immediately. In a large call stack or blocked main thread, it can be delayed significantly.
Another scenario: nested promises
Promise.resolve()
.then(() => {
console.log("First");
return Promise.resolve();
})
.then(() => {
console.log("Second");
});
These chained promises queue in microtask queue sequentially.
Node.js follows a slightly different model based on libuv.
It has six phases:
Node also has:
process.nextTick() is even higher priority than promises in Node.
JavaScript isn't truly asynchronous, but with the help of the Event Loop, it behaves as if it were. This illusion is what makes JavaScript such a powerful language for handling non-blocking I/O and UI responsiveness.
To recap:
Mastering the Event Loop allows you to debug effectively, write non-blocking applications, and fully understand what’s happening under the hood of every async function, await statement, or setTimeout() call.
It’s not magic, it’s the Event Loop.
Software Engineer
Senior Software Engineer