🌀 Understand how JavaScript handles asynchronous code e.g the Event Loop,call stack..
JavaScript is single-threaded, meaning it can only do one thing at a time. But sometimes, we need it to handle multiple tasks “at once” — like waiting for data while still responding to button clicks.
That’s where asynchronous programming comes in.
To make this possible, JavaScript uses a few key parts:
- Call Stack
- Web APIs
- Event Loop
- Task Queue
- Microtask Queue
🧱 1. Call Stack
- The Call Stack is where JavaScript keeps track of what function is currently running.
- When you call a function, it’s pushed onto the stack.
- When it finishes, it’s popped off the stack.
- JavaScript can only run one function at a time, so if the stack is busy, everything else waits.
- Note: It’s LIFO (Last In, First Out) — the last function added runs first.
🪣 Example:
function myName() {
myAge()
console.log("name")
}
function myAge() {
console.log("I am 23")
}
myName()
Explanation:
The first function pushed is myName().
Before it finishes, it calls myAge(), which is added to the top of the stack.
Since it’s LIFO, myAge() runs first and prints "I am 23", then myName() continues and prints "name".
🌐 2. Web APIs
- These are features provided by the browser, not by JavaScript itself.
- Examples: setTimeout, fetch, DOM events, etc.
- When you use one of these (like setTimeout), the browser handles it outside the main JavaScript thread.
🕒 Example:
When you call setTimeout, it’s sent to the Web API timer.
After the delay, the Web API sends the callback to the Task Queue.
🔁 3. Event Loop
- The Event Loop is like a watcher.
- It constantly checks if the Call Stack is empty.
- If the stack is empty and there are tasks waiting in the Task Queue, it moves the next one to the stack to run.
🚦 Think of it like traffic control:
When the road (Call Stack) is clear, the next car (task) from the queue is allowed to go.
A queue uses FIFO (First In, First Out) — the first task waiting goes first.
📥 4. Task Queue (Callback Queue)
- This is where asynchronous tasks (like setTimeout, click events, or network requests) wait after finishing in the Web APIs.
- The Event Loop moves tasks from here to the stack when it’s free.
🧩 Example:
setTimeout(() => {
console.log('Some stuff done')
}, 2000)
After 2 seconds, this callback is placed in the Task Queue and waits until the stack is empty.
⚙️ 5. Microtask Queue
- This is like a VIP queue — it has higher priority than the Task Queue.
- It’s used by things like Promises (.then, .catch) and MutationObservers.
- Before the Event Loop takes anything from the Task Queue, it first empties all Microtasks.
✨ Example:
If you have both a setTimeout and a Promise.then, the Promise runs first — even if both are ready at the same time.
🧩 Summary
| Component | Purpose | Example |
|---|---|---|
| Call Stack | Runs the current code line by line | getWater() running |
| Web APIs | Handle async tasks outside the main thread | setTimeout, fetch |
| Event Loop | Moves tasks from queues to call stack when ready | Keeps checking if stack is free |
| Task Queue | Holds callbacks waiting to run | setTimeout callback |
| Microtask Queue | Holds promise callbacks (runs first) | Promise.then() |
🧱 Synchronous Example
function getWater() {
console.log('Getting water')
doSomething()
console.log('Here is water')
}
function cook() {
console.log('Start cooking')
}
function doSomething() {
console.log('Doing some stuff...')
const start = Date.now()
const delay = 5000 // 5 seconds
while (Date.now() - start < delay) {
// Busy wait (blocks everything)
}
console.log('Some stuff done')
}
getWater()
cook()
What happens step-by-step:
- getWater() runs → prints “Getting water”.
- Then it runs doSomething(), which blocks everything for 5 seconds.
- After that, it prints “Some stuff done”.
- Finally,cook() runs → prints “Start cooking”.
Because everything runs synchronously, cooking starts only after the delay.
⚡ Asynchronous Example
function getWater() {
console.log('Getting water')
doSomething()
console.log('Here is water')
}
function cook() {
console.log('Start cooking')
}
function doSomething() {
console.log('Doing some stuff...')
setTimeout(() => {
console.log('Some stuff done')
}, 2000)
}
getWater()
cook()
💡 Now it behaves differently.
Here’s why: setTimeout is asynchronous — it doesn’t block the main thread.
Step-by-step:
- getWater() runs → prints “Getting water”.
- doSomething()runs → schedules a timer in Web APIs, then continues.
- It prints “Here is water”.
- cook() runs → prints “Start cooking”.
- After 2 seconds, when the timer finishes, the callback (“Some stuff done”) goes to the Task Queue.
- The Event Loop waits for the Call Stack to be empty, then runs the task.
🔁 Event Loop Interview Example
Understand the priority between Promises and setTimeout:
setTimeout(() => {
console.log("timeout")
}, 0)
Promise.resolve().then(() => {
console.log("promise")
})
Expected Output:
promise
timeout
Explanation:
- Even though setTimeout has 0ms delay, it goes to the Task Queue.
- Promise.then() goes to the Microtask Queue, which has higher priority.
- The Event Loop empties the Microtask Queue first, so "promise" prints before "timeout".
✅ Key Takeaway: Microtasks (Promises) run before tasks (setTimeout) in the Event Loop.
🔁 In short
The Event Loop is like a traffic officer — it keeps checking if the main road (Call Stack) is clear, and if it is, it lets the next waiting car (task) from the queue pass through.
That’s how JavaScript handles asynchronous behavior, even though it runs on a single thread. 🚦