Today, I learned one of the most important concepts in JavaScript: the Event Loop and the Concurrency Model.
JavaScript is Single-Threaded
JavaScript is a single-threaded language. This means it can execute only one piece of JavaScript code at a time.
For example:
console.log("A");
console.log("B");
console.log("C");
Output:
A
B
C
JavaScript executes these statements one after another.
What is the Call Stack?
The Call Stack is where JavaScript executes functions. Whenever a function is called, it is added to the stack. After the function finishes executing, it is removed from the stack.
Example:
function one() {
two();
}
function two() {
console.log("Hello");
}
one();
Execution order:
one()
↓
two()
↓
console.log()
↓
Stack becomes empty
The Call Stack follows the Last In, First Out (LIFO) principle.
What are Web APIs and Node APIs?
Some operations take time, such as:
- Waiting for a timer
- Making an API request
- Reading a file
- Listening for button clicks
JavaScript itself cannot perform these operations.
Instead, it asks the runtime to handle them.
In Browsers
The browser provides Web APIs such as:
setTimeout()setInterval()fetch()addEventListener()- DOM APIs
localStorage
In Node.js
Node.js provides Node APIs such as:
fs.readFile()fs.writeFile()pathoshttp
These APIs perform asynchronous work outside the JavaScript engine.
Callback (Macrotask) Queue
When an asynchronous operation like setTimeout() finishes, its callback does not execute immediately.
Instead, it is placed into the Callback Queue, also called the Macrotask Queue.
Example:
setTimeout(() => {
console.log("Timer");
}, 1000);
After one second Call back function added to Callback Queue.
It waits there until the Event Loop moves it to the Call Stack.
Microtask Queue
The Microtask Queue has a higher priority than the Callback Queue.
The following callbacks are added to the Microtask Queue:
Promise.then()Promise.catch()Promise.finally()awaitqueueMicrotask()
Example:
Promise.resolve().then(() => {
console.log("Promise");
});
The callback is placed in the Microtask Queue.
Event Loop
The Event Loop is responsible for checking whether the Call Stack is empty.
When the Call Stack becomes empty, it:
- Executes all Microtasks
- Executes one Callback (Macrotask)
- Again checks call stack is empty or not and does 1&2.
It does not execute code itself. It simply moves callbacks from the queues to the Call Stack.
Why do Promises execute before setTimeout()?
Consider this example:
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
Output:
Start
End
Promise
Timeout
Why?
-
Promise.then()goes to the Microtask Queue. -
setTimeout()goes to the Callback (Macrotask) Queue. - The Event Loop always executes all Microtasks first before executing Macrotasks.
Does setTimeout() execute exactly on time?
A common misunderstanding is that:
setTimeout(callback, 1000);
means the callback will execute exactly after one second.
This is not true.
It means:
Execute the callback after at least one second.
If the Call Stack is busy with synchronous code, the callback has to wait.
Example:
setTimeout(() => {
console.log("Done");
}, 1000);
// Imagine this loop takes 3 seconds
for (let i = 0; i < 1e10; i++) {}
console.log("Finished");
Output:
Finished
Done
Even though the timer finished after one second, the callback waited until the Call Stack became empty.
What is the JavaScript Concurrency Model?
JavaScript can execute only one line of code at a time, but it can still handle many asynchronous operations.
For example:
- A timer can be counting.
- A network request can be downloading.
- The browser can listen for button clicks.
All these operations are handled by the browser or Node.js while JavaScript continues executing other code.
When these operations finish, their callbacks are added to the appropriate queue.
This ability to make multiple operations progress during the same period of time is called the Concurrency Model.
Cooperative Concurrency vs True Parallelism
These two terms are often confused.
Cooperative Concurrency
Only one JavaScript task runs at a time.
Tasks take turns executing.
Example:
One chef starts cooking rice, then makes tea while the rice cooks.
The chef is not doing both tasks at the exact same moment, but both tasks are progressing.
JavaScript works like this.
True Parallelism
Multiple tasks execute at the exact same time using different CPU cores or threads.
Example:
- Chef 1 cooks rice.
- Chef 2 makes tea.
- Chef 3 cuts vegetables.
All three are working simultaneously.
Conclusion
Learning the Event Loop and the Concurrency Model helped me understand how JavaScript performs asynchronous operations without blocking the main thread. These concepts explain why Promise callbacks execute before setTimeout(), why timers are not always exact, and how browsers and Node.js work together with JavaScript to build responsive applications.
Understanding these fundamentals is essential for writing efficient JavaScript code.
Thank you for reading! If you're also learning JavaScript, I hope this summary helps you understand these concepts more clearly. Happy coding!
Top comments (0)