Event Loop and Callback Queue in JavaScript

The event loop is the secret behind JavaScript's asynchronous programming. Before we start the event loop discussion, I want you to look into some high-level topics of call stack. JavaScript call stack is a mechanism to keep track of multiple function calls or we can say call stack is to manage multiple execution contexts.

In this article we are going to discuss:

  1. Web APIs
  2. Callback Queue
  3. Microtask Queue
  4. Event loops
  5. How starvation happens

Please have a look at the below diagram to get a better idea of the topics we are discussing.

event loop

Web APIs

Web APIs or Browser APIs are built into your web browser and are able to expose data from the browser and surrounding computer environment and do useful complex things with it. setTimeout(), DOM APIs, fetch(), local storage, location, and console are some examples for Web APIs. They are not part of the JavaScript language, but they are built on top of the core JavaScript language, providing you with extra superpowers to use in your JavaScript code. 

In these web APIs, the browser is actually using some complex lower-level code (e.g. C++ or Rust). But, this complexity is abstracted away from you by the API.

Callback Queue(Macrotask Queue)

Callback Queue or Macrotask Queue is where the callback function gets pushed to, and waits for the execution. Let us dive deep into the callback queue with an example.

setTimeout(abc, 5000);

function abc(){

Here, we have a callback function abc(), that is to be executed after 5 seconds of time. After 5 seconds, the callback function abc() does not get directly pushed into the call stack for execution instead it will be pushed into the callback queue. The call stack may be busy with the execution of some other functions. The callback queue keeps all callback functions in a queue that are ready for execution and waits until the call stack gets free. When the call stack is free, the event loop pops the callback function from callback queue and pushes it to the call stack. 

The callback queue was working under the First In First Out(FIFO) principle.

Microtask Queue

Microtask Queue is similar to the Callback Queue(macrotask queue), but microtask queue has a higher priority than the callback queue. All the callback functions coming through promises and mutation observer will go inside the microtask queue. 

For example, in the case of fetch(), it returns a promise and, this callback function will get pushed into the microtask queue. Promise handling always has a higher priority than setTimeout or setInterval. So the event loop gives higher priority to the microtask queue and then looks into callback queue.

Event loops

The event loop keeps running continuously and monitoring the call stack and callback queue. When the call stack gets empty and the event loop sees some callback functions waiting in the microtask queue or callback queue(macrotask queue) for execution. Then, the event loop pops the callback functions one by one from the microtask queue and callback queue and gets pushed into the call stack for execution. The event loop acts like a gatekeeper for the callback queue. 

The microtask queue has higher priority than the callback queue(macrotask queue).

How starvation happens

Let us take a scenario of a callback function from the microtask queue create another callback function, and get pushed into the microtask queue. If this process goes in a loop, then the functions in the callback queue(macrotask queue) will not get any opportunity to execute. This is called starvation in the callback queue.