What is the Event Loop in Javascript?

The event loop can be described as a mechanism that manages function calls and ensures that tasks are executed in order.

What is the Event Loop in Javascript?

Picture this: You're browsing the web and come across an impressive website with stunning visuals and seamless interactions. As you navigate through its pages, everything feels smooth, and buttons respond instantly to your clicks. On the flip side, imagine visiting another site, and as you interact with it, the buttons become unresponsive, and the whole page freezes for a moment. Frustrating, right?

What's the secret behind these vastly different experiences? It all boils down to the event loop in JavaScript - a fundamental concept that plays a crucial role in building responsive web applications.

gif from GIPHY

So what is the event loop?

In JavaScript the event loop can be described as a mechanism that manages function calls and ensures that tasks, including asynchronous events, are executed in order, allowing JavaScript to handle both synchronous and asynchronous operations efficiently.

Imagine you're a kid waiting for your parent to come home with milk. Your parent, who is at work, winds up, and heads to the station where the only form of transport is a motorcycle that carries one passenger at a time and takes them to their destination. Afterwards, it comes back to the station, picks up another passenger, and repeats the process. And the one making sure no one jumps the line and everyone gets home safe is a guard posted at the station. The event loop in JavaScript is like the guard at the station who manages tasks (function calls) and ensures they are done in the order they arrive. They keep track of who's waiting in line (the tasks to be executed) and this process repeats until everyone gets home i.e. (every function gets returned).

However, let's say your parent realizes they forgot the milk and they need to go back and get it. They leave the line (the task/function is paused), fetch the milk, and return to the station. If everyone else in line is ready to go, by the time your parent returns, they’ll find everyone else has already been taken home. Now it's their turn, the guard lets them hop on and in a few minutes you’re enjoying your milk.

This cycle of tasks being managed in order is like the event loop, efficiently handling tasks, including asynchronous events, and making sure everything gets done effortlessly.

gif from GIPHY

Before we continue let me touch on an important point here, asynchronous programming.

Asynchronous programming is a powerful approach that allows JavaScript to handle time-consuming tasks without blocking the entire program's execution.

In traditional synchronous programming, tasks are executed one after another, and the program waits for each task to complete before moving on to the next. This approach works well for simple applications, but it becomes problematic when dealing with time-consuming operations like data fetching from remote servers.

Asynchronous programming, on the other hand, enables tasks to be executed independently, allowing the program to initiate a task and move on to other operations without waiting for the task to finish. The event loop plays a crucial role in managing these asynchronous tasks by monitoring their progress and ensuring that their results are handled appropriately.

Consider the example of fetching data from a remote server using the fetch API. Instead of waiting for the server's response synchronously, the program can initiate the fetch request and continue executing other tasks. When the server responds, the event loop places the callback function responsible for processing the server's response into the callback queue. As soon as the call stack becomes empty, the event loop dequeues the callback function and executes it, allowing the program to handle the fetched data.

Too many words, let's look at some code and allow me to ground my examples in reality.

Example 1

Let's say you have a JavaScript program running on your IDE. The program executes the following:

console.log("First");

console.log("Second");

console.log("Third");

In this ideal program, the output will be 'First', 'Second', and 'Third', exactly in that order. Why? Because JavaScript executes code synchronously, meaning tasks are performed in the order they appear.

As the program runs, the console.log methods are added to the call stack in the order they are encountered. Each method is executed, printing its message to the console, and then removed from the call stack, allowing the following method to be executed. This process continues until all the methods have been executed in the order they were called, resulting in the 'First', 'Second', and 'Third' messages being displayed in that order.

Example 2

Now let’s take it up a notch with another example.

console.log("First");

function main() {
let x = 0;

  while (x < 100000) {
    x++;
  }

  console.log("Second");
}

console.log("Third");

main();

When we run the above example, the output will be 'First', 'Third', and 'Second'.

But what’s changed?

Let me explain.

As before, the program starts with the first task, which is console.log("First");. It is immediately added to the call stack and printed to the console.

Next, we encounter the main() function call. At this point, the main() function gets added to the call stack, and we enter the loop inside the main() function. This loop is designed to perform a large number of iterations, making it a computationally expensive task.

As the loop runs, it consumes significant processing time and becomes a blocking function. This means we will have to wait for the main() function to complete its computation before it gets removed from the call stack, and the event loop then proceeds to the next method, console.log("Second");, to execute and display 'Second' on the console. Finally, the event loop moves on to the last method: console.log("Third"); up the queue and executes it, printing 'Third' on the console.

Side note - We referred to the main() function’s loop as a "blocking function".

Let's clarify this term.

In synchronous programming, when a time-consuming task like the loop inside main() runs, it occupies the main thread, effectively blocking any other operations from taking place. During this time, the program waits for the loop to complete before moving on to the next task. In real-world scenarios, this could lead to unresponsive web apps, as the user interface freezes until the loop finishes.

Before we proceed to our final example, it's worth mentioning two essential concepts in modern JavaScript development: async/await and error handling.

1. Async/Await: JavaScript offers the async/await syntax, a more concise and readable way to work with promises. While we won't dive deep into promises here, it's important to know that async/await allows you to write asynchronous code, that is easier to read and understand. It simplifies working with promises and complements the event loop's management of asynchronous tasks.

2. Error Handling: Handling errors properly is crucial for building robust applications. In our final example, we've included error handling for the fetch request. This ensures that if something goes wrong during an asynchronous operation, the application can gracefully recover and continue running without crashing. The event loop's careful handling of errors allows the program to remain responsive, even in the face of unexpected situations.

Example 3

For my last example, let me show you how the event loop would affect a somewhat real-world project. Here's a link to the Codesandbox so you can see it in action: Event Loop Sandbox.

We are going to fetch some data from Json Placeholder a Free fake API for testing and prototyping.  Read more.

const button = document.getElementById("myBtn");
const outputContainer = document.getElementById("user-container");

function fetchData() {
  console.log("Data fetching process initiated!");
  fetch("https://jsonplaceholder.typicode.com/users")
    .then((response) => {
      if (!response.ok) {
        throw new Error("Something went wrong");
      }
      return response.json();
    })
    .then((data) => {
      let result = data.slice(0, 5);

      // Introducing a delay of 2 seconds using setTimeout
      setTimeout(() => {
        console.log(result);
        console.log("fetched");

        // Display the fetched data in our page
        let outputHTML = "<ul class='users-list'>";
        result.forEach((user) => {
          outputHTML += `<li>${user.name}</li>`;
        });
        outputHTML += "</ul>";
        outputContainer.innerHTML = outputHTML;
      }, 2000); // 2 seconds delay
    })
    .catch((error) => {
      console.error("An error occurred while fetching the data", error);
    });
}

console.log("Start fetching data...");

button.addEventListener("click", fetchData);

Take a second to think about what the sequence of events will be.

So here’s how the final example will go.

At first, when you open the program, the JavaScript runs, and the console will print out "Start fetching data...". Soon after you click the “Check Users” button, another text will show up in the console, “Data fetching process initiated!”. The script then fetches user data from the JSONPlaceholder API. During the data retrieval process, we introduce a 2-second delay using setTimeout to simulate real-world scenarios with API calls or other asynchronous tasks.

This is where the event loop steps in to keep the program responsive. While waiting for the data to be fetched, the event loop ensures that other tasks, like rendering the button animation, continue to run smoothly. After the delay, the fetched data is displayed on the webpage inside an unordered list, the data also gets printed out in the console, and finally “fetched” gets printed out.

codesandbox terminal

As you can see, the event loop's power lies in its ability to manage and prioritize tasks. It ensures that your application remains interactive and responsive, even when dealing with potentially time-consuming operations.

In conclusion, understanding the event loop is crucial for writing efficient and responsive JavaScript code. As developers, understanding how the event loop works allows us to create applications that can handle complex tasks without sacrificing user experience.

If you find this article enjoyable and helpful, you can subscribe to my newsletter, Just in Time, It's free and I can send you more nuggets of wisdom about tech and programming straight to your inbox so you're always up to date with the tech world.