Feb 1, 2018Last modified May 11, 2025

Deep dive on event loop in javascript

Javascript is single threaded (this single thread is referred as main thread). This means that javascript engines can only execute one piece of code at a time.

Event loop is the mechanism that allows javascript to handle asynchronous operations without blocking the main thread.

Event loop constantly monitors the call stack and the callback queues. When the call stack is empty, it takes the first event from the callback queue and pushes it to the call stack.

Why do we need the event loop though ?

Given that javascript is single threaded, whenever a function takes a long time to execute ( e.g network requests, timers etc), it would block the main thread, causing the application to freeze.

We can avoid this by offloading the long running tasks. This is where the event loop comes into picture. The event loop constantly monitors the call stack and the callback queues. When the call stack is empty, it takes the first event from the callback queue and pushes it to the call stack.

Example #1

    console.log("Start");

    setTimeout(()=>{
        console.log("3 seconds completed");
    }, 3000);

    console.log("End");

You will notice that the "3 seconds completed" message is printed after "End" message. This is because the setTimeout function is executed asynchronously and is pushed to the callback queue once 3 seconds are completed.

Example #2

    console.log('Start');

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

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

    console.log('End');

JavaScript actually has multiple queues with different priorities:

  • Microtask queue (higher priority) - for promises, MutationObserver
  • Macrotask queue - for setTimeout, setInterval, DOM events

How event loop facilitates asynchronous operations ?

When you initiate an asynchronous operation (e.g., setTimeout, fetch, addEventListener), the following generally happens:

  1. The asynchronous task (e.g., setting a timer, making an HTTP request) is started.
  2. The handling of this task is often delegated to browser Web APIs (for browser environments) or Node.js APIs (for Node.js environments). These APIs operate outside the main JavaScript engine.
  3. Once the asynchronous operation completes (e.g., the timer expires, the HTTP request returns), a callback function associated with that operation is placed in the Event Queue.
  4. The Event Loop continuously checks the Call Stack.
  5. If the Call Stack is empty, the Event Loop takes the first callback from the Event Queue and pushes it onto the Call Stack for execution.

Event loop in action

I loaded youtube.com in my browser and recorded the performance using the browser's performance tab. Here's how it looks -

From this performance recording, we can see the Event Loop in action through:

  • The sequential execution of "Tasks" on the main thread.
  • The triggering of tasks by events like "Timer fired," which originate from asynchronous operations managed outside the main thread and then brought back in via the event queue.
  • The processing of results from asynchronous operations like network requests within "Tasks."