We write JavaScript every day. We use async/await, setTimeout, make API calls. But have you ever wondered β how does all of this actually work?
Today I'm going to break down one of the most important yet least understood topics in JavaScript β The Event Loop.
π€ What Exactly Is the Event Loop?
JavaScript is a single-threaded language. That means it can only do one thing at a time. But we do many things simultaneously β API calls, timers, user click events β all at once!
How is this possible?
π The answer is the Event Loop!
The Event Loop is the mechanism that makes JavaScript behave non-blocking and asynchronous.
π§ The Main Parts of JavaScript Runtime:
text
βββββββββββββββββββββββββββββββ
β Call Stack β
β (What's running now) β
βββββββββββββββββββββββββββββββ€
β Web APIs / Node APIs β
β (Browser/Backend features)β
βββββββββββββββββββββββββββββββ€
β Callback Queue β
β (Tasks waiting in line) β
βββββββββββββββββββββββββββββββ€
β Event Loop β
β (The traffic controller) β
βββββββββββββββββββββββββββββββ
π― Let's Understand Each Part:
1οΈβ£ Call Stack (The Doer)
This is where your code actually runs
It's a LIFO (Last In, First Out) structure
Functions are pushed onto the stack when called, and popped off when they return
Example:
javascript
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function calculate() {
const sum = add(5, 10);
const result = multiply(sum, 2);
console.log(result);
}
calculate();
How the stack works:
calculate() β pushed to stack
add(5, 10) β pushed to stack (runs, returns, popped)
multiply(15, 2) β pushed to stack (runs, returns, popped)
console.log(30) β pushed to stack (runs, popped)
calculate() β popped from stack
Stack is now empty! β
2οΈβ£ Web APIs / Node.js APIs (The Helpers)
These are provided by the browser or Node.js (not JavaScript itself)
Examples: setTimeout, fetch, DOM events, fs.readFile
They handle async operations in the background while the stack runs other code
Example:
javascript
console.log('Start');
setTimeout(() => {
console.log('Inside setTimeout');
}, 2000);
console.log('End');
What happens step by step:
console.log('Start') β runs immediately
setTimeout β handed off to Web API (timer starts)
console.log('End') β runs immediately (doesn't wait!)
After 2 seconds, the callback moves to the queue
Event Loop checks if the stack is empty β then puts it on the stack
Output:
text
Start
End
Inside setTimeout
3οΈβ£ Callback Queue (The Waiting Area)
Also called Task Queue or Macro Task Queue
Holds callbacks waiting to be executed
Follows FIFO (First In, First Out) order
Types of Queues:
Macro Task Queue: setTimeout, setInterval, I/O, UI rendering
Micro Task Queue: Promise.then, MutationObserver, queueMicrotask
Micro Task Queue has higher priority! β‘
4οΈβ£ Event Loop (The Traffic Controller)
The Event Loop is a continuous running process
It constantly checks: Is the Call Stack empty?
If empty, it takes tasks from the queue and pushes them to the stack
The Priority Order:
First, execute all Micro Tasks (Promises)
Then execute one Macro Task (setTimeout)
Repeat!
Visualization:
text
Event Loop continuously checks:
Is Call Stack empty? β YES β Take from Queue β Push to Stack
β NO β Wait
π₯ Let's See It All Together:
What do you think the output will be? π€
javascript
console.log('1οΈβ£ Start');
setTimeout(() => {
console.log('2οΈβ£ setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('3οΈβ£ Promise');
});
console.log('4οΈβ£ End');
Output:
text
1οΈβ£ Start
4οΈβ£ End
3οΈβ£ Promise
2οΈβ£ setTimeout
Why this order?
Start and End β immediately on the stack
setTimeout β sent to Web API, then Macro Task Queue
Promise β sent to Micro Task Queue (higher priority!)
Event Loop processes all Micro Tasks first β Promise runs
Then processes Macro Tasks β setTimeout runs
Surprised? Most developers are! π
π‘ Why This Matters:
β
Avoid Blocking the Main Thread
javascript
// β BAD - This will block everything for 5 seconds!
function heavyTask() {
const start = Date.now();
while (Date.now() - start < 5000) {
// Nothing - just wasting time!
}
console.log('Done');
}
// β
GOOD - Use async operations
function heavyTask() {
setTimeout(() => {
console.log('Done');
}, 5000);
}
β
Understand Async Code
javascript
// The classic interview question:
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// Output: 3, 3, 3 β (Not 0, 1, 2!)
// Why? By the time setTimeout runs, i is already 3!
Fix using let instead of var (creates block scope):
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0, 1, 2 β
}, 1000);
}
β
Write Better Code
Make API calls properly with async/await
Handle promises correctly
Use setTimeout(0) to defer execution
π Key Takeaways:
Concept What It Does
Call Stack Executes functions (synchronous)
Web APIs Handles async tasks in background
Task Queue Holds callbacks waiting to run
Micro Task Queue Holds promises (Higher priority!)
Event Loop Moves tasks from queue to stack when empty
π― Summary:
JavaScript is single-threaded but non-blocking
The Event Loop is the mechanism that makes async possible
Micro Tasks (Promises) run before Macro Tasks (setTimeout)
Understanding the Event Loop helps you write better, faster, non-blocking code
Always think about "What's on the stack?" when debugging async issues
π¬ Let's Discuss:
Question for you: Have you ever faced a situation where your async code didn't behave as expected? How did you debug it?
Drop your experience in the comments! π
Top comments (0)