JavaScript Event Loop
JavaScript can only execute one piece of code at a time.
While JavaScript is busy, other JavaScript code must wait.
How do JavaScript handle timers, user clicks, downloads, and asynchronous operations?
The answer is the Event Loop.
Understanding the Event Loop
Understanding the Event Loop explains many common JavaScript behaviors.
- Why
setTimeout(..., 0)does not execute immediately. - Why long loops freeze a page.
- Why Promises execute before timers.
- Why
awaitdoes not block the browser. - Why click events wait until current code has finished.
JavaScript Executes One Task at a Time
JavaScript executes code in sequence.
Only one function can execute at any given moment.
Each function must finish before the next one can begin.
Examples
function step1() {
myDisplayer("Step 1");
}
function step2() {
myDisplayer("Step 2");
}
step1();
step2();
Output:
Step 1
Step 2
function step1() {
myDisplayer("Step 1");
}
function step2() {
myDisplayer("Step 2");
}
step1();
step2();
Output:
Step 2
Step 1
The Browser Performs Asynchronous Work
JavaScript itself does not wait for timers or download web pages.
Instead, the browser performs these operations while JavaScript continues running.
Examples include:
- Timers
- Network requests
- User events
- File loading
When an operation finishes, JavaScript is notified that the result is ready.
The Event Loop
The Event Loop continuously checks whether JavaScript is busy.
When JavaScript finishes its current task, the Event Loop starts the next waiting task.
This process repeats for as long as the page is open.
JavaScript starts
↓
Run code
↓
Start timer
↓
Continue running
↓
Timer finishes
↓
Event Loop
↓
Run callback
The Event Loop coordinates asynchronous operations and decides when JavaScript should execute the next task.
Example: setTimeout()
The callback is not executed immediately.
Instead, it waits until JavaScript finishes its current work.
Example
myDisplayer("A");
setTimeout(function() {
myDisplayer("B");
}, 2000);
myDisplayer("C");
Output:
A
C
B
The timer finishes almost immediately.
However, the callback cannot run until JavaScript finishes executing the current code.
The Event Loop starts the callback after the current task has completed.
Example: Promise
Example
myDisplayer("A");
Promise.resolve().then(function() {
myDisplayer("B");
});
myDisplayer("C");
Output:
A
C
B
Why did the Promise callback run before the timer?
The answer is that Promise callbacks are placed in a special queue with higher priority.
Task Queue and Microtask Queue
JavaScript uses two main queues for asynchronous work.
| Queue | Contains |
|---|---|
| Task Queue | Timers, events, messages |
| Microtask Queue | Promise handlers and awai |
After each task finishes, JavaScript processes all waiting microtasks before starting the next task.
Comparing Timers and Promises
| Operation | Queue |
|---|---|
| setTimeout() | Task Queue |
| setInterval() | Task Queue |
| Click event | Task Queue |
| Promise.then() | Microtask Queue |
| catch() | Microtask Queue |
| finally() | Microtask Queue |
| await continuation | Microtask Queue |
Long Tasks Block JavaScript
If JavaScript executes a long-running task, everything else must wait.
During this time, the browser cannot execute other JavaScript code.
User clicks, timers, and completed downloads must wait until the current task finishes.
Example
The browser becomes unresponsive while wait() is running.
function wait() {
let i = 4e9;
while (--i > 0);
}
myDisplayer("Start");
wait();
myDisplayer("Done");
Example Explained
Why did the example take a long time?
Why was "Start" and "Done" diplayed at the same time?
The questions lead directly to how browsers update the page.
Example
The code executes like this:
myDisplayer("Start");
wait();
myDisplayer("Done");
Step 1
This code:
myDisplayer("Start");
changes the DOM:
<p id="demo">Start</p>
But the browser does not repaint the screen immediately.
Step 2
starts the long loop.
wait();
While this loop is running:
- JavaScript is busy
- The Event Loop cannot run
- The browser cannot repaint the page
So although the DOM already contains "Start", the user still doesn't see it.
Step 3
The loop finally finishes.
myDisplayer("Done");
Now the DOM becomes:
<p id="demo">Start Done</p>
Step 4
The JavaScript task is finished.
Only now does the browser get a chance to repaint the page.
So the user sees:
Start Done
Diplayed both at once.
The Timeline
JavaScript starts
↓
myDisplayer("Start")
(DOM changes)
↓
wait()
(JavaScript busy)
↓
myDisplayer("Done")
(DOM changes again)
↓
JavaScript finishes
↓
Browser repaints
↓
Screen shows:
Start Done
The example above is a good Event Loop example.
It demonstrates that there are two separate things:
- Updating the DOM
- Updating the screen (rendering)
The DOM is updated immediately, but the browser must wait until the current JavaScript task has finished before repainting.
Want to show "Start" first?
Example
You have to give the browser a chance to repaint before running the long task:
function wait() {
let i = 4e9;
while (--i > 0);
}
myDisplayer("Start");
setTimeout(function () {
wait();
myDisplayer("Done");
}, 0);
The Timeline
JavaScript starts
↓
Display "Start"
↓
Current task finishes
↓
Browser repaints
(Start becomes visible)
↓
Event Loop
↓
Run wait()
↓
Display "Done"
↓
Browser repaints
Summary
- JavaScript executes one task at a time.
- The browser performs asynchronous operations such as timers, downloads, and user events.
- Completed operations wait until JavaScript is ready.
- The Event Loop starts waiting tasks when the current task has finished.
- Promise handlers and
awaitcontinuations run before timers because they use the Microtask Queue.