JavaScript Event Loop: How Does JavaScript Handle Multiple Tasks?
JavaScript is often called single-threaded . That means it has one main thread and can execute one piece of JavaScript at a time . So here is the obvious question: If JavaScript can do only one thing at a time, how can it handle all of this? API requests Timers Button clicks User input File operations Animations For example: console . log ( " Start " ); setTimeout (() => { console . log ( " Timer…
JavaScript is commonly referred to as single-threaded, meaning it can execute only one piece of JavaScript code at a time using its main thread. Given this limitation, the question arises as to how JavaScript manages to handle multiple tasks simultaneously. These tasks include API requests, timers, button clicks, user input, file operations, and animations.
Consider the following example:
```
console.log("Start");
setTimeout(() => {
console.log("Timer finished");
}, 2000);
console.log("End");
```
The output is:
```
Start
End
Timer finished
```
Why is this the case? If JavaScript had to wait for the timer to finish, the output should have been:
```
Start
Timer finished
End
```
The explanation lies in the Event Loop, one of the most crucial concepts in JavaScript. To fully grasp the Event Loop, we need to understand the entire system, which consists of five components:
1. The Call Stack - Execution Arena
The Call Stack is where JavaScript executes functions. It acts like a stack of tasks, with the last task added being the first one executed. For instance:
```
function first() {
second();
}
function second() {
console.log("Hello");
}
first();
```
The Call Stack processes the functions step by step:
```
Call Stack
first()
↓
second()
↓
console.log("Hello")
```
As each function completes, it is removed from the stack. This synchronous execution of code tasks is why JavaScript is typically synchronous by default.
2. Web APIs - Browser's Asynchronous Powerhouse
The browser offers powerful features called Web APIs, which handle asynchronous work. Some examples include:
```
setTimeout
fetch
DOM events
addEventListener
Geolocation
```
Continuing with our previous example:
```
console.log("Start");
setTimeout(() => {
console.log("Timer finished");
}, 2000);
console.log("End");
```
Here's what happens:
- `console.log("Start")` is pushed onto the Call Stack and executed, producing the output "Start".
- The timer is registered with the browser's Web APIs.
- `console.log("End")` is executed immediately, outputting "End".
3. Callback Queue - Waiting for Asynchronous Completion
Once an asynchronous operation finishes, its callback can be placed into the Callback Queue, also known as the Task Queue. Web APIs handle this queue. For example:
```
setTimeout(() => {
console.log("Timer finished");
}, 2000);
```
After two seconds, the callback `console.log("Timer finished")` is moved into the Callback Queue:
```
Callback Queue
[ console.log("Timer finished") ]
```
The callback cannot execute yet because the Call Stack must be empty.
4. The Event Loop - The Traffic Controller
The Event Loop continuously checks whether the Call Stack is empty. If it is, the Event Loop moves waiting work from the queues (Callback Queue and Microtask Queue) back into the Call Stack. The process can be visualized as:
```
Is Call Stack Empty?
┌────────┴────────┐
No │ Yes
▼ │ ▼
Keep executing Move next task to the Call Stack
```
Thus, the timer flow becomes:
```
setTimeout(...)
↓
Web APIs
Wait 2 seconds
↓
Callback Queue
↓
Event Loop checks Call Stack
↓
Callback executes
```
This is why the callback runs after the specified delay.
5. Microtasks - A Secondary Queue
In addition to the Callback Queue, there is another queue called the Microtask Queue. Microtasks typically include Promise-related callbacks, such as `.then()`, `.catch()`, `.finally()`, and `queueMicrotask()`. For instance:
```
console.log("Start");
Promise.resolve().then(() => {
console.log("Promise");
});
```
The execution order here is:
```
Call Stack
Promise.resolve()
↓
Microtask Queue
[ console.log("Promise") ]
↓
Event Loop checks Call Stack
↓
Call Stack is empty
↓
Callback executes (console.log("Start"))
↓
Microtask Queue processed
↓
Output: Start, Promise
```
The Event Loop handles these microtasks after executing the current stack of tasks (in this case, after "Start"). The "Promise" output follows the "Start" because the microtask queue is processed after the Call Stack is empty.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.