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");
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
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.
Asynctasks (Promises likeEand Timeouts likeF) 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");
The Output ๐
A: script start
B: class added
C: layout read 0
F: script end
and only then...
D: Promise.then
E: setTimeout
What Matters Here ๐ง
-
classList.add()is synchronous. -
getBoundingClientRect()is synchronous (and expensive!). It forces the browser to calculate geometry right now. -
Promiseswait. 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.
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)
This is not the Event Loop. This is just... The Line.
๐งฉ Part III Summary
- Synchronous execution always comes first.
- The Call Stack fully owns the Main Thread.
- Render cannot interrupt it.
- Promises and timers must wait for the stack to clear.
- 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)