Lesson 99 – JavaScript Event Loop and Concurrency
JavaScript is single-threaded, which means it can execute one task at a time. However, thanks to the event loop, JavaScript can handle asynchronous operations like network requests, timers, and DOM events efficiently without blocking the main thread.
In this lesson, we’ll break down how the JavaScript Event Loop works, including key concepts like the call stack, web APIs, callback queue, and task/microtask queues.
The Call Stack
The call stack is where JavaScript keeps track of what function is currently running and what function to return control to when the current function finishes.
Example:
function greet() {
console.log("Hello!");
}
function start() {
greet();
console.log("Start done!");
}
start();
Here’s how the stack works:
start()
is pushed to the stack.start()
callsgreet()
, sogreet()
is pushed to the stack.greet()
finishes and is popped off.- Control returns to
start()
, which prints "Start done!" and is then popped off.
Web APIs (Browser APIs)
When asynchronous operations like setTimeout
, fetch
, or event listeners are called, they are handed off to the Web APIs provided by the browser.
These tasks are run outside of the main thread and queued for execution once the call stack is empty.
Callback Queue (Task Queue)
Once a Web API task completes, its callback is sent to the callback queue (also known as the task queue). The event loop checks if the call stack is empty and then pushes the next callback from the queue to the stack.
Example:
console.log("Start");
setTimeout(() => {
console.log("Timeout callback");
}, 0);
console.log("End");
Output:
Start
End
Timeout callback
Even with setTimeout(..., 0)
, the callback goes to the task queue, so it runs after the synchronous code.
Microtask Queue
The microtask queue handles promises and queueMicrotask callbacks. It has higher priority than the task queue.
Example with Promise:
console.log("Start");
Promise.resolve().then(() => {
console.log("Promise resolved");
});
console.log("End");
Output:
Start
End
Promise resolved
Even though it’s asynchronous, Promise.then
runs before setTimeout
because it's placed in the microtask queue, which runs before the task queue.
Event Loop in Action
The event loop does the following repeatedly:
- Check if the call stack is empty.
- If yes, process all tasks in the microtask queue.
- Then move to the task queue and process one task.
- Repeat the cycle.
Summary
- JavaScript is single-threaded but handles async tasks via the event loop.
- Call stack handles function execution.
- Web APIs handle async tasks (like timers, network calls).
- Microtask queue (Promises) has higher priority than task queue (setTimeout, events).
- The event loop coordinates when each task is executed.
Try Your Hand
Challenge:
Predict the output of the following code:
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
Expected Output:
1
4
3
2
Explain why the output appears in that order.
Stay curious and keep experimenting with async code!