DEV Community

Cover image for The Event Loop, Part III: The Unstoppable Train ๐Ÿš‚
Elina Dzhelilova
Elina Dzhelilova

Posted on

The Event Loop, Part III: The Unstoppable Train ๐Ÿš‚

One track. One train. No overtaking.
๐Ÿ‘‹ Hey folks!

In Part I and Part II, we discussed the Illusion: why browsers appear "instant" to Muggles (users) ๐Ÿง‘โ€, and why that illusion breaks when we interact with the DOM.

Now, we strip away the magic and look at the foundation.
No Event Loop yet.
No microtasks.
No queues.

Only the thing that always happens first.


๐Ÿš‚ The Call Stack is a Train That Does Not Stop

Imagine the Hogwarts Express.

  • It has one track (The Main Thread ๐Ÿงต).
  • It moves in one direction.
  • While the train is on the track, nothing else can move.

This is your Call Stack.

JavaScript is synchronous by nature. It is single-minded. It executes spells (code) step-by-step and never gives up control halfway through.

The browser? Sure, itโ€™s asynchronous. It has networking, timers, the rendering engine, the GPUโ€ฆ But JavaScript itself? It sits comfortably in the Call Stack, blocking the view.

And while the Call Stack is busy, the track is closed. โ›”๏ธ


๐Ÿงช Scenario 1: The Sync Trap (Click vs. .click())

Letโ€™s look at a spell that confuses many wizards. We have a button, a listener, and some logs.

console.log("A: script start");

button.addEventListener("click", () => {
  console.log("B: click start");

  // ๐Ÿช„ Synchronous array magic
  const items = [1, 2, 3];
  items.map((n) => {
    console.log("C: map:", n);
    return n * 2;
  });

  button.classList.add("active");
  console.log("D: class added");

  // ๐Ÿฆ‰ Future work (Async)
  Promise.resolve().then(() => {
    console.log("E: Promise.then");
  });

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

  console.log("G: click end");
});

console.log("H: before click");

// โš ๏ธ Crucial moment: We trigger the click SYNCHRONOUSLY
button.click();

console.log("I: after click");

console.log("J: script end");
Enter fullscreen mode Exit fullscreen mode

The Output ๐Ÿ“œ

A: script start
H: before click
B: click start
C: map: 1
C: map: 2
C: map: 3
D: class added
G: click end
I: after click
J: script end
E: Promise.then
F: setTimeout
Enter fullscreen mode Exit fullscreen mode

Why This Happens (The Magic Mechanics) โš™๏ธ
Look closely at I: after click. It appears after the entire click handler runs (logs B, C, D, and G).

Why? Because button.click() is a synchronous command.

The script runs H: before click.

It hits button.click() and immediately jumps into the addEventListener function.

It must finish that entire function (running through G: click end) before it can return to the main flow to print I: after click and J: script end.

The Rule: The train is moving. Passengers (code lines) exit strictly in boarding order. Async tasks (Promises like E and Timeouts like F) are thrown out of the window to catch a later train. ๐Ÿ‘‹


๐Ÿงช Scenario 2: Immediate Magic (No Clicks)

console.log("A: script start");

const el = document.createElement("div");

// ๐Ÿช„ Direct DOM mutation
el.classList.add("box");
console.log("B: class added");

// โš ๏ธ Forces Layout (Heavy synchronous calculation)
const height = el.getBoundingClientRect().height;
console.log("C: layout read", height);

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

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

console.log("F: script end");
Enter fullscreen mode Exit fullscreen mode

The Output ๐Ÿ“œ

A: script start
B: class added
C: layout read 0
F: script end
Enter fullscreen mode Exit fullscreen mode

and only then...

D: Promise.then
E: setTimeout
Enter fullscreen mode Exit fullscreen mode

What Matters Here ๐Ÿง 

  • classList.add() is synchronous.
  • getBoundingClientRect() is synchronous (and expensive!). It forces the browser to calculate geometry right now.
  • Promises wait. Even if they feel โ€œinstantโ€, they are not allowed on the current train.

No async mechanism can intervene until the Call Stack is empty.


๐Ÿš— Where Render Fits In

Rendering (Layout, Paint, Composite) is not part of the Call Stack. Think of Render as the Flying Ford Anglia ๐Ÿš™ trying to cross the train tracks.

  • The Ford Anglia is hovering, ready to go.
  • The engine is roaring (60fps needed!).
  • The GPU is warmed up.

But: โ›”๏ธ The Ford Anglia cannot cross while the Hogwarts Express ๐Ÿš‚ (Call Stack) is occupying the track.

Elina ยท WebMagic animation: Blue Flying Ford Anglia representing the Browser Render Engine trying to fly alongside the Main Thread tracks

It doesnโ€™t matter how urgent the paint is. If your while loop is running, or your JSON is parsing, or your map() is iterating, the frame is dropped. The screen freezes. The illusion breaks.


๐Ÿง‘โ€๐ŸŽ“ Who Actually Sits in the Call Stack?

When we say โ€œCall Stackโ€, we donโ€™t mean abstract code. We mean specific synchronous operations that hog the line:

โœ… Array.map, filter, reduce
โœ… for while loops
โœ… classList.add() / remove()
โœ… getBoundingClientRect() / offsetWidth
โœ… Any function without await

All of them run to completion. All of them fully occupy the Main Thread. None of them let Render sneak in.

๐Ÿ˜ˆ Who Can Stop the Train?

The Call Stack cannot stop itself. Once the function starts, it runs until the closing brace.

Only external โ€œDark Artsโ€ can stop it:

๐Ÿ‘‘ The Browser (killing a frozen tab)
๐ŸงŠ Debugger (breakpoints)
๐Ÿ’ฅ Crash (Error)

Elina ยท WebMagic animation: The Hogwarts Express train (Call Stack) being attacked and stopped by Death Eaters, representing heavy synchronous tasks blocking the Main Thread

This is not the Event Loop. This is just... The Line.

๐Ÿงฉ Part III Summary

  1. Synchronous execution always comes first.
  2. The Call Stack fully owns the Main Thread.
  3. Render cannot interrupt it.
  4. Promises and timers must wait for the stack to clear.
  5. The train moves until it has completed its route.

๐Ÿ”ฎ Whatโ€™s Next?

We have the Line. Now we need the Loop.

In Part IV, we finally meet the messengers:

  • ๐Ÿฆ‰ Microtasks
  • ๐Ÿ’Œ Promises

Why does the Owl (Promise) arrive before the Render, but after the sync code? Stay tuned! โœจ

๐Ÿ’ฌ Let's Discuss

Have you ever accidentally frozen your browser with a while(true) loop or heavy calculation? Drop your "horror stories" in the comments! ๐Ÿ‘‡

Happy coding! ๐Ÿ‘ฉโ€๐Ÿ’ปโœจ

Elina ยท WebMagic Mastering the Dark Arts of Web Development

Top comments (0)