DEV Community

Cover image for JS Event Loop Explained
Syed Noman Bukhari
Syed Noman Bukhari

Posted on

1

JS Event Loop Explained

Introduction:

The Event Loop is a cornerstone of JavaScript’s execution model, making it an essential concept for developers to understand. As a single-threaded language, JavaScript relies on the Event Loop to handle asynchronous operations like network requests, timers, and user interactions without blocking the main thread. By orchestrating the execution of tasks, the Event Loop ensures that JavaScript remains responsive and efficient, even when dealing with complex or time-consuming processes. Mastering the Event Loop empowers developers to write more predictable, performant, and bug-free code, making it a critical skill for navigating the dynamic nature of modern web development.

Understanding the Event Loop: A Simplified Guide

The Event Loop is the heart of JavaScript's concurrency model, ensuring non-blocking, smooth execution of code. It helps manage tasks efficiently and keeps the application responsive. Here's how it works, broken down into its key components:

  1. Call Stack
    The Call Stack is where functions are executed. It's like a stack of tasks being processed one at a time. When a function is called, it’s added to the stack, and once completed, it’s removed. JavaScript runs on a single thread, meaning it handles one task at a time.

  2. Web APIs
    Some functions, like setTimeout or setInterval, are not executed directly in the Call Stack. Instead, they are handled by Web APIs (built into the browser or Node.js). These APIs take care of tasks like timers, HTTP requests, or DOM events without blocking the main thread.

  3. Task Queues
    Once Web APIs complete their tasks, the results (or callbacks) are sent to Task Queues. A Task Queue is a waiting area for tasks to be executed after the current Call Stack is clear. Different queues have different priorities:
    Microtask Queue: Handles tasks like Promise callbacks and MutationObserver. These are executed before other tasks.
    Macro Task Queue/Callback Queue: Handles tasks like setTimeout, setInterval, or events like click and load. Macro Task Queue is also called as Callback Queue

  4. Event Loop
    The Event Loop keeps an eye on the Call Stack and Task Queues. It works like this:
    If the Call Stack is empty, the Event Loop checks the Task Queues for tasks.
    Tasks in the Microtask Queue are prioritized and executed first.
    After that, tasks from the Macro Task Queue are executed.

Code Example 1

console.log('Start');

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

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

console.log('End');
Enter fullscreen mode Exit fullscreen mode

Output

Start
End
Promise
Timeout
Enter fullscreen mode Exit fullscreen mode

Here’s what happens:

  1. console.log('Start') and console.log('End') execute in the Call Stack. They will be executed first as they were already in the Call Stack Queue.
  2. setTimeout sends its callback to the Macro Task Queue.
  3. Promise sends its callback to the Microtask Queue.
  4. The Event Loop executes the Promise callback before the Timeout callback.

A Common Misconception (Why setimout with 0 MS interval didn't execute first):

You can see in the above example the timeout has timer of 0 MS but still we're seeing "Promise" output printed first. Why is it? Because our once the Call Stack queue is empty there is nothing to print or execute in it, at this time Event Loop goes to Task Queues as the priority of Micro Task queue is higher than Macro Task Queue so it will go to Mirco Task Queue first, pick that chunk of code from there and place it in Call Stack queue and our Call Stack Queue immediately prints it. Than, Event Loop will go to Macro Task Queue as the Micro Task Queue is empty and pick that chunk of code from there and place it Call Stach Queue and our Call Stack queue will executes it.

Code Example 2

console.log('Start');

setTimeout(() => {
  console.log('Timeout 1');
  Promise.resolve().then(() => console.log('Promise inside Timeout 1'));
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise 1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('Promise 2');
    setTimeout(() => console.log('Timeout inside Promise'), 0);
  });

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

console.log('End');

Enter fullscreen mode Exit fullscreen mode

Output

Start
End
Promise 1
Promise 2
Timeout 1
Promise inside Timeout 1
Timeout 2
Timeout inside Promise

Enter fullscreen mode Exit fullscreen mode

Step by step explanation

  1. Synchronous tasks execute first:
  • console.log('Start') → Prints Start.
  • console.log('End') → Prints End.
  1. Asynchronous tasks are queued:
  • setTimeout(() => console.log('Timeout 1'), 0) → Adds a callback to the Macro Task Queue.
  • Promise.resolve().then(() => console.log('Promise 1')) → Adds Promise 1 to the Microtask Queue.
  1. Microtasks execute before Macrotasks:
  • Promise 1 → Prints Promise 1 and queues the next .then for Promise 2 in the Microtask Queue.
  • Promise 2 → Prints Promise 2 and schedules a new setTimeout callback (inside Promise) to the Macro Task Queue.
  1. Macro tasks execute in order:
  • Timeout 1 → Prints Timeout 1 and schedules a new Promise (Promise inside Timeout 1) in the Microtask Queue.
  • Promise inside Timeout 1 → Executes immediately after Timeout 1, printing Promise inside Timeout 1.
  1. Remaining Macro tasks execute:
  • Timeout 2 → Prints Timeout 2.
  • Timeout inside Promise → Prints Timeout inside Promise.

Above example demonstrates how tasks from different queues interact and how nested asynchronous operations are handled by the Event Loop.

Conclusion

To wrap up, let’s recap the key insights about the Event Loop. At its core, the Event Loop is what enables JavaScript to handle asynchronous operations efficiently while remaining single-threaded. By understanding how the Call Stack, Web APIs, Task Queue, and Microtask Queue interact, you’ve unlocked the ability to write more predictable, performant, and bug-free code. This knowledge empowers you to tackle asynchronous programming with confidence, whether it involves managing setTimeout, handling Promises, or using async/await effectively.

As you dive deeper, consider exploring tools like Chrome DevTools, which provides a powerful interface for debugging async code. Using its Performance tab or Call Stack traces, you can observe the Event Loop in action and gain even greater mastery over your code execution.

Lastly, I’d love to hear your thoughts! Was this guide helpful in demystifying the Event Loop? Do you have any questions or insights of your own? Drop your comments below, share the article with your network, and let’s keep the conversation going. Together, we can make understanding the Event Loop accessible to everyone. Happy Coding!

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay