This content originally appeared on DEV Community and was authored by Jonathan Idioseph
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.
This content originally appeared on DEV Community and was authored by Jonathan Idioseph