Demystifying async behavior so you can predict it, without memorizing diagrams.
1. Why this matters (in plain English)
The Problem That Made Me Google at 2 AM
I was building a dashboard in Next.js.
Everything worked… except my loading spinner.
It just froze.
No error, no crash, it just stopped spinning.
Turns out, I had no clue I’ve met the event loop, just… indirectly. This guide strips the jargon and gives you a mental model you’ll actually use.
2. Think of JavaScript as a Restaurant
- Kitchen = Call Stack (where cooking happens)
- Waiter = Event Loop (decides what order to serve next)
-
Two waiting lines:
-
Fast line = Promises (
.then()
,async/await
) → “urgent orders” -
Slow line = Timers (
setTimeout
, clicks, network) → “big meal orders”
-
Fast line = Promises (
3. The Secret Rule Nobody Told Me
Before serving any big orders, the waiter must finish all "urgent orders" first.
That’s why a Promise
often runs before a setTimeout
, even if the timeout is set to 0
.
Example 1: Why setTimeout(0)
still feels “late”
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
Output:
Start
End
Promise
Timeout
Why?
- JavaScript finishes the current work (
Start
,End
) - Runs Promises (urgent orders) →
"Promise"
- Then runs setTimeout (big orders) →
"Timeout"
Example 2: The silent performance bug with async/await
Bad:
await task1();
await task2(); // Waits for task1 before even starting task2
Better:
await Promise.all([task1(), task2()]); // Both start together
If tasks don’t depend on each other, run them in parallel. Saves time.
4. How This Saved My Spinner
My “frozen” spinner was because I ran a heavy loop that never let the Event Loop breathe.
for (let i = 0; i < 1e9; i++) {} // freezes everything
Fix: Break work into chunks so the UI gets a chance to update.
function doWorkInChunks() {
let i = 0;
function chunk() {
for (let j = 0; j < 100000; j++) i++;
if (i < 1e9) setTimeout(chunk); // let UI breathe
}
chunk();
}
5. Why You Should Care
Understanding the Event Loop means:
- Faster apps
- Less “freezing” UIs
- Predictable async behavior
Even if you’re not “deep into backend stuff,” this is frontend survival skill.
6. Myths vs Reality (quick de-mystification)
-
Myth:
setTimeout(fn, 0)
runs immediately. Reality: It waits until all microtasks finish. -
Myth:
async/await
is always faster than Promises. Reality: It’s about ordering. Sequentialawait
can be slower thanPromise.all
. - Myth: The event loop is “browser stuff only.” Reality: Node.js has the loop too (with its own task sources); the microtask > macrotask rule still applies.
7. When to care (and when not to)
- Care when: spinners freeze, progress bars stutter, “0ms” timers feel late,
await
feels slow. - Don’t overthink it when: the page is simple and nothing is heavy—ship it.
8. Debug checklist you can copy
- Is something “late”? → Check for a Promise running first.
- Is something “slow”? → Run independent tasks in parallel with
Promise.all
. - Is UI freezing? → Break big loops with
setTimeout
/requestIdleCallback
. - Still weird? → Log order:
console.log("A")
, Promise.then("B")
,setTimeout("C", 0)
.
9. Too Long; Didn't Read (stick this in your notes)
1). Sync finishes → 2) All microtasks run → 3) One macrotask runs → repeat.
Use Promise.all
for parallel work; slice heavy loops so the UI can breathe.
Your turn: Ever had a bug that only happened because of “JavaScript timing magic”? Drop it in the comments, I might try to explain it in plain English.
Top comments (1)