JavaScript’s Event Loop Explained Visually — Once and for All
Manali Khattar

Manali Khattar @khattarmanali

About: Full-stack developer | 3+ yrs exp | MERN stack | I build real-world apps, write dev articles, and share what I learn to help others grow.

Location:
raipur chhattisgarh
Joined:
Nov 4, 2022

JavaScript’s Event Loop Explained Visually — Once and for All

Publish Date: Jun 19
0 0

Ever wondered why console.log() inside a setTimeout(..., 0) runs after everything else?

Or why your Promise logs show up before your timer, even if both seem async?

Welcome to the world of the Event Loop — one of JavaScript’s most powerful (and misunderstood) features.

In this post, I’ll walk you through exactly how the Event Loop works using:

  • Minimal theory
  • Real-world examples
  • Simple visual diagrams

Let’s dig in.

What’s Inside the JavaScript Runtime?
Before we talk about the Event Loop, here’s a quick overview of the runtime environment.

+--------------------+
|    Call Stack      |  ← Executes your functions
+--------------------+
         ↓
+--------------------+
|     Web APIs        |  ← Timers, fetch, DOM
+--------------------+
         ↓
+------------------------+
|   Macrotask Queue       | ← setTimeout, I/O
+------------------------+
         ↓
+------------------------+
|   Microtask Queue       | ← Promises, async/await
+------------------------+
         ↓
+--------------------+
|    Event Loop       | ← Coordinates everything
+--------------------+

Enter fullscreen mode Exit fullscreen mode

Synchronous Example

function sayHi() {
  console.log("Hi");
}
sayHi();

Enter fullscreen mode Exit fullscreen mode

This runs top to bottom inside the call stack.

Asynchronous Example with setTimeout

console.log("Start");

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

console.log("End");

Enter fullscreen mode Exit fullscreen mode

Output:

Start
End
Timer

Enter fullscreen mode Exit fullscreen mode

Why?
Because setTimeout doesn’t go directly on the call stack. It goes to Web APIs, waits for the timer, and then gets added to the macrotask queue, which only runs when the call stack is empty.

Microtasks vs Macrotasks
Try this code:

console.log("Start");

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

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

console.log("End");

Enter fullscreen mode Exit fullscreen mode

Output:

Start
End
Microtask
Macrotask

Enter fullscreen mode Exit fullscreen mode
  • Promises (microtasks) always run before setTimeouts (macrotasks), even if both are async.
  • That’s how the Event Loop prioritizes execution.

Visual: The Event Loop Flow

[ Is Call Stack Empty? ]
         ↓
     Run All Microtasks
         ↓
     Run One Macrotask
         ↓
         Repeat

Enter fullscreen mode Exit fullscreen mode

This is what makes JavaScript non-blocking — and yet single-threaded.

async/await in Action

async function demo() {
  console.log("1");
  await Promise.resolve();
  console.log("2");
}

demo();
console.log("3");

Enter fullscreen mode Exit fullscreen mode

Output:

1
3
2

Enter fullscreen mode Exit fullscreen mode

await splits the function. The part after await is queued as a microtask, and it runs after the current call stack completes.

Takeaways

  • JavaScript is single-threaded, but async thanks to the Event Loop.
  • Microtasks (Promises) run before Macrotasks (setTimeout, fetch, etc.).
  • Use this mental model to debug better and write cleaner async code.

Want to Visualize It?
I've created this Event Loop diagram to help you internalize the flow:

event

Wrapping Up
The Event Loop isn’t just theory — it directly affects how your code runs. Whether you’re dealing with React effects, API calls, or DOM events, mastering this concept can help you avoid weird bugs and timing issues.

Let me know what confused you the most about the Event Loop when you first learned it.

And if you found this helpful, consider giving it a ❤️ or sharing it with a fellow developer.

Comments 0 total

    Add comment