Asia/Dhaka
Posts

Understanding the JavaScript Event Loop

October 30, 2024
JavaScript is a single-threaded programming language. This means it has one Call Stack and one Memory Heap. It can only execute one piece of code at a time. If JavaScript is single-threaded, how can we make network requests that take seconds to complete without freezing the entire browser? How does setTimeout work? The answer lies in the Event Loop and the environment the JavaScript engine runs in (like the Browser or Node.js). To understand the Event Loop, we need to understand the moving parts:
  1. The Call Stack: This is where the V8 engine (or whichever JS engine you use) keeps track of what function is currently executing. If a function calls another function, it gets pushed onto the top of the stack. When a function returns, it's popped off the stack.
  2. Web APIs (Browser) / C++ APIs (Node.js): JavaScript itself cannot make an HTTP request or set a timer. These are features provided by the environment. When you call setTimeout or fetch, JavaScript passes the callback function to the Web API and continues executing the next line of code in the Call Stack. It does not wait.
  3. The Callback Queue (Task Queue): When a Web API finishes its job (e.g., the timer expires, or the HTTP request returns), it doesn't push the callback immediately back onto the Call Stack. That would interrupt whatever code is currently running. Instead, it pushes the callback into the Callback Queue.
  4. The Microtask Queue: This is a special, high-priority queue specifically for Promises (.then, .catch, async/await) and MutationObserver.
The Event Loop is a remarkably simple mechanism. It is a continuous loop that constantly checks two things:
  1. Is the Call Stack empty?
  2. Is there anything in the Queues?
Here is the exact order of operations:
  1. Execute all code currently in the Call Stack until it is completely empty.
  2. Check the Microtask Queue (Promises). If there are tasks here, push them onto the Call Stack one by one and execute them until the Microtask Queue is completely empty.
  3. Check the Callback Queue (Macrotasks like setTimeout, DOM events). If there are tasks here, take the first one, push it onto the Call Stack, and execute it.
  4. Repeat the process.
Javascript
console.log('1. Start');

setTimeout(() => {
  console.log('2. Timeout');
}, 0);

Promise.resolve().then(() => {
  console.log('3. Promise');
});

console.log('4. End');
What is the output?
  1. console.log('1. Start') goes on the Call Stack and runs immediately.
  2. setTimeout goes on the Call Stack. The browser takes the callback, sets a timer for 0ms, and pops setTimeout off the stack. Because it's 0ms, the callback immediately goes to the Callback Queue.
  3. Promise.resolve().then() goes on the Call Stack. The callback goes to the Microtask Queue.
  4. console.log('4. End') goes on the Call Stack and runs immediately.
  5. The Call Stack is now empty. The Event Loop checks the Microtask Queue. It finds the Promise callback, moves it to the Call Stack, and executes console.log('3. Promise').
  6. The Call Stack is empty again. The Microtask Queue is empty. The Event Loop checks the Callback Queue. It finds the Timeout callback, moves it to the Call Stack, and executes console.log('2. Timeout').
Output: 1. Start, 4. End, 3. Promise, 2. Timeout. Understanding the Event Loop is crucial for avoiding performance bottlenecks, preventing UI freezes, and writing efficient, bug-free asynchronous JavaScript.
On this page
Hey! Got questions?Ask me anything 👋