JavaScript’s Most Misunderstood Feature: The Event Loop Isn’t What You Think



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”

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. Sequential await can be slower than Promise.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