Why Won’t JavaScript Chill? Understanding the Event Loop

Eftee Codes
3 min readOct 29, 2024

--

JavaScript’s event loop is a game-changer for handling asynchronous tasks on a single thread. If you’ve ever wondered how JavaScript juggles tasks like API calls, timers, or file reads without slowing down, the event loop holds the answers!

In this guide, you’ll dive into each part of the event loop with interactive code snippets and analogies that make it easy to understand and apply.

What is the Event Loop?

The event loop is what keeps JavaScript’s engine running smoothly, managing tasks in a single-threaded, non-blocking way. Essentially, the event loop helps JavaScript manage synchronous and asynchronous code execution without any collisions.

The Call Stack: Where Code Runs

The call stack is the first place JavaScript code goes when it’s about to run. Think of the call stack like a stack of dishes: the latest task is always on top and gets handled first.

Example: A Simple Stack of Tasks

function firstTask() {
console.log("First task");
}

function secondTask() {
console.log("Second task");
}

firstTask(); // added to the stack
secondTask(); // added to the stack after firstTask completes
  • Run this code to see each function execute in order.
  • Notice that tasks run top-to-bottom — one finishes before the next starts.

Web APIs: Helpers for Asynchronous Tasks

When JavaScript encounters tasks that are asynchronous, like setTimeout, fetch, or event listeners, it hands them off to Web APIs (browser-provided helpers). This keeps the call stack clear while the asynchronous task completes elsewhere.

Example: Using setTimeout with Web APIs

console.log("Start");

setTimeout(() => {
console.log("Inside setTimeout");
}, 1000);

console.log("End");
  • You’ll see “Start” and “End” log immediately, while “Inside setTimeout” waits for 1 second before appearing.
  • This delay occurs because setTimeout is an asynchronous operation handled by the Web API

Callback Queue: Waiting for the Event Loop

Once a Web API completes a task (like a setTimeout delay), it pushes the result to the callback queue. The event loop checks the call stack, and if it’s empty, it moves tasks from the callback queue to the call stack to be executed.

Example: Checking the Callback Queue

console.log("Start");

setTimeout(() => {
console.log("From Callback Queue");
}, 0);

console.log("End");
  • You’ll notice “Start” and “End” print before “From Callback Queue” even though setTimeout was set to 0 milliseconds.
  • This delay happens because the callback queue has to wait for the call stack to be empty before it runs.

The Event Loop in Action

Let’s look at a scenario with a few more tasks to see how everything works together.

console.log("Start");

setTimeout(() => {
console.log("Timeout 1");
}, 0);

Promise.resolve().then(() => console.log("Promise 1"));

console.log("End");

Walkthrough:

  1. “Start” and “End” print immediately since they’re in the call stack.
  2. Promise 1 is a microtask and runs right after the synchronous code completes.
  3. Timeout 1 waits in the callback queue and runs last, even though its delay is 0.

Microtasks vs. Macrotasks: Key Differences

  • Microtasks: Executed immediately after the call stack is empty (e.g., Promises).
  • Macrotasks: Tasks like setTimeout or setInterval, which get queued for later.

Example: Microtasks and Macrotasks in Action

console.log("Start");

setTimeout(() => {
console.log("Macrotask");
}, 0);

Promise.resolve().then(() => console.log("Microtask"));

console.log("End");
  • “Microtask” logs before “Macrotask” even though both are async because microtasks have priority over macrotasks.

Conclusion: Making Sense of the Event Loop

In JavaScript, the event loop plays a vital role in juggling both synchronous and asynchronous tasks on a single thread, ensuring smooth execution without blocking. Here’s the core process: the call stack handles immediate, synchronous tasks, while Web APIs manage asynchronous actions like timers, network calls, and event listeners to keep the stack clear. Once an async task completes, it joins the callback queue, waiting for the call stack to empty before running. Among async tasks, microtasks (such as promises) have priority over macrotasks (like setTimeout), giving them faster access to the call stack. With these essentials, the event loop empowers JavaScript to handle complex, dynamic web experiences effortlessly.

Happy Coding!

--

--

Eftee Codes
Eftee Codes

Written by Eftee Codes

Full Stack Developer | Tech Enthusiast | Psychology & Economics Aficionado | Turning Code into Fun Experiences 🎮🧠

No responses yet