๐ JavaScript Event Loop & Concurrency Model: A Beginner's Guide
"JavaScript is single-threaded, yet it can handle timers, API requests, user interactions, and asynchronous tasks without freezing the application. How is that possible? The answer lies in the Event Loop and JavaScript's Concurrency Model."
When I first started learning JavaScript, one question always confused me:
If JavaScript can execute only one task at a time, how does it perform multiple tasks like API calls, timers, and user interactions simultaneously?
The answer is JavaScript's Event Loop and Concurrency Model.
In this article, we'll understand how JavaScript manages asynchronous operations behind the scenes using simple explanations, diagrams, and practical examples.
๐ What You'll Learn
- What is JavaScript's Concurrency Model?
- Why JavaScript is Single-Threaded
- Call Stack
- Web APIs / Node APIs
- Callback Queue (Macrotask Queue)
- Microtask Queue
- Event Loop
-
setTimeout()vs Promises - Cooperative Concurrency
- Concurrency vs Parallelism
Let's begin!
๐งต JavaScript is Single-Threaded
JavaScript executes code using a single thread, meaning it can perform only one operation at a time.
Imagine a chef working alone in a kitchen.
If the chef starts cooking one dish, they cannot cook another dish until the first one is finished.
Similarly, JavaScript executes one statement at a time.
Example:
console.log("Task 1");
console.log("Task 2");
console.log("Task 3");
Output
Task 1
Task 2
Task 3
Everything runs sequentially.
๐ค Then How Does JavaScript Handle Multiple Tasks?
Modern applications need to:
- Fetch data from servers
- Wait for timers
- Read files
- Handle button clicks
- Play videos
If JavaScript waited for every operation to finish before continuing, web pages would freeze.
Instead, JavaScript uses a Concurrency Model.
โ๏ธ What is the Concurrency Model?
The Concurrency Model allows JavaScript to manage multiple tasks efficiently without executing them simultaneously on the main thread.
JavaScript delegates time-consuming operations to the browser (Web APIs) or Node.js (Node APIs). While those operations run outside the JavaScript engine, the main thread continues executing other code.
When the asynchronous task finishes, its callback is queued and executed later by the Event Loop.
๐งฑ Call Stack
What is the Call Stack?
The Call Stack is a data structure that keeps track of function execution.
It follows the LIFO (Last In, First Out) principle.
Whenever a function is called:
- It is pushed onto the stack.
- When it finishes, it is popped off.
Example
function first() {
console.log("First");
}
function second() {
first();
console.log("Second");
}
second();
Output
First
Second
Execution
Call Stack
โ
second()
โ
first()
โ
console.log()
โ
first() removed
โ
console.log()
โ
second() removed
๐ Web APIs / Node APIs
JavaScript itself cannot perform operations like timers or network requests.
These features are provided by the environment.
Browser Web APIs
- setTimeout()
- setInterval()
- fetch()
- DOM Events
- localStorage
- Geolocation
Node.js APIs
- File System (fs)
- HTTP Server
- Streams
- Timers
- Process APIs
These APIs perform asynchronous work while JavaScript continues executing other code.
Example
console.log("Start");
setTimeout(() => {
console.log("Timer Completed");
}, 2000);
console.log("End");
Output
Start
End
Timer Completed
Notice that JavaScript doesn't wait for two seconds.
๐ฅ Callback Queue (Macrotask Queue)
Once an asynchronous operation completes, its callback is placed inside the Callback Queue (also called the Macrotask Queue).
Examples of Macrotasks
- setTimeout()
- setInterval()
- DOM Events
- setImmediate() (Node.js)
Example
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
console.log("End");
Output
Start
End
Timeout
Even though the timer delay is 0 milliseconds, the callback executes only after the Call Stack becomes empty.
โก Microtask Queue
The Microtask Queue stores higher-priority asynchronous tasks.
Examples include:
- Promise.then()
- Promise.catch()
- Promise.finally()
- queueMicrotask()
Microtasks always execute before macrotasks.
Example
console.log("Start");
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output
Start
End
Promise
๐ Event Loop
What is the Event Loop?
The Event Loop is a scheduler.
Its job is to continuously monitor the Call Stack.
Whenever the Call Stack becomes empty, it checks:
- Microtask Queue
- Callback Queue
The Event Loop first executes all microtasks.
Only after the Microtask Queue is empty does it execute one macrotask.
Event Loop Workflow
JavaScript Code
โ
โผ
Call Stack
โ
โผ
Async Operation
โ
โผ
Web APIs / Node APIs
โ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ Microtask Queue โ
โ Promise.then() โ
โ queueMicrotask() โ
โโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโ
โ Callback Queue โ
โ setTimeout() โ
โ DOM Events โ
โโโโโโโโโโโโโโโโโโโโ
โ
โผ
Event Loop
โ
โผ
Call Stack
๐ setTimeout() vs Promises
One of the most common interview questions is the execution order of setTimeout() and Promises.
Example
console.log("Start");
setTimeout(() => {
console.log("setTimeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output
Start
End
Promise
setTimeout
Why Does Promise Execute First?
Let's understand the execution step by step.
Step 1
console.log("Start");
Output
Start
Step 2
setTimeout(...)
The timer is registered with the Web API.
Step 3
Promise.resolve().then(...)
The callback is placed inside the Microtask Queue.
Step 4
console.log("End");
Output
End
Step 5
The Call Stack becomes empty.
The Event Loop checks the Microtask Queue first.
Output
Promise
Step 6
After all microtasks finish, the Event Loop executes the Callback Queue.
Output
setTimeout
๐ค Cooperative Concurrency
JavaScript uses cooperative concurrency.
This means:
- Only one piece of JavaScript code executes at a time.
- Tasks voluntarily yield control after they finish.
- No task interrupts another task.
Example
console.log("Task 1");
setTimeout(() => {
console.log("Task 2");
}, 0);
console.log("Task 3");
Output
Task 1
Task 3
Task 2
โก Concurrency vs Parallelism
Many beginners think these terms mean the same thing, but they are different.
Concurrency
Managing multiple tasks by switching between them efficiently.
Example:
A chef preparing multiple dishes by moving between them.
Parallelism
Executing multiple tasks at the exact same time using multiple workers or CPU cores.
Example:
Three chefs cooking three different dishes simultaneously.
JavaScript and Parallelism
JavaScript's main thread is not parallel.
However, true parallelism can be achieved using:
- Web Workers (Browser)
- Worker Threads (Node.js)
These create separate threads for CPU-intensive work.
๐ Microtask Queue vs Callback Queue
| Feature | Microtask Queue | Callback Queue |
|---|---|---|
| Priority | Higher | Lower |
| Examples | Promise.then(), catch(), finally(), queueMicrotask() | setTimeout(), setInterval(), DOM Events |
| Execution | Runs immediately after Call Stack is empty | Runs after all Microtasks finish |
๐ Key Takeaways
- JavaScript is single-threaded.
- The Call Stack executes synchronous code.
- Web APIs and Node APIs handle asynchronous operations.
- Completed asynchronous callbacks are placed into queues.
- Microtask Queue has higher priority than the Callback Queue.
- The Event Loop continuously checks the Call Stack and schedules tasks.
-
Promises always execute before
setTimeout()callbacks because microtasks are processed first. - JavaScript achieves concurrency through the Event Loop, while true parallelism requires Web Workers or Worker Threads.
๐ฏ Conclusion
Understanding the Event Loop and JavaScript's Concurrency Model is essential for writing efficient asynchronous code. Concepts like the Call Stack, Web APIs, task queues, and the Event Loop explain why JavaScript can remain responsive even while handling timers, network requests, and user interactions.
Once you understand these fundamentals, topics like async/await, API calls, and modern frameworks such as React and Node.js become much easier to learn because they all build on the same asynchronous execution model.
Top comments (0)