The Event Loop — JavaScript’s Biggest Lie (It’s Single-Threaded… Sort Of)

LOGIC • TECH • CODE
*Published on Rabhish — Real Actionable Building Habits, Ideas & Skill for Humans*

If you’ve ever been in a JavaScript interview, you’ve heard this line: “JavaScript is single-threaded.”

You nod. The interviewer nods. Everyone pretends they fully understand what that means.

But here’s the thing — if JavaScript can only do one thing at a time, how does it handle thousands of users, run timers, fetch APIs, and read files all at once without freezing?

The answer is the Event Loop. And once you understand it, JavaScript stops being confusing and starts being beautiful.

Let’s Start With a Kitchen

Imagine you’re a chef. Alone. One stove, one counter, one pair of hands. That’s JavaScript — a single-threaded runtime. You can only chop one vegetable at a time.

“But here’s the trick: you don’t stand and watch the water boil. You put the pot on, set a timer, and start chopping something else. When the timer dings — you go back.”

That’s the Event Loop in a nutshell. JavaScript doesn’t wait. It delegates, moves on, and comes back when things are ready.

The Actual Architecture

Here’s what’s happening under the hood:

Your Code (Call Stack)
    ↓
Is it synchronous? → Execute immediately
Is it async? → Send to Web APIs / libuv
    ↓
When done → Push callback to the Queue
    ↓
Event Loop checks: Is the Call Stack empty?
    Yes → Pick from Queue → Push to Call Stack → Execute
    No → Wait

Three key players:

1. Call Stack

Where your code actually runs, one function at a time

2. Web APIs / libuv

Where async tasks (timers, HTTP requests) go to wait

3. Callback Queue

Where completed tasks line up, waiting for the stack to be free

The Event Loop is just a bouncer. It constantly asks: “Is the Call Stack empty? No? Wait. Yes? Let the next callback in.”

The Code That Breaks People’s Brains

console.log(“First”);

setTimeout(() => {
  console.log(“Second”);
}, 0);

console.log(“Third”);

Output:
First
Third
Second

Wait — setTimeout is set to 0 milliseconds. Zero! Why doesn’t “Second” print second?

Because setTimeout — even with 0ms — is an async operation. It gets sent to the Web API, then to the Callback Queue. The Event Loop won’t pick it up until the Call Stack is completely empty.

Microtasks vs Macrotasks (The VIP Queue)

Not all async callbacks are equal. There are two queues:

Microtask Queue (VIP): Promises, process.nextTick(), MutationObserver

Macrotask Queue (Regular): setTimeout, setInterval, setImmediate, I/O callbacks

The Event Loop always empties the Microtask Queue first before touching any Macrotask.

setTimeout(() => console.log(“Timeout”), 0);

Promise.resolve().then(() => console.log(“Promise”));

console.log(“Sync”);

Output:
Sync
Promise
Timeout

The Promise (microtask) jumps ahead of setTimeout (macrotask). Every. Single. Time. This isn’t a race condition — it’s by design.

Why This Matters in Real Life

Understanding the Event Loop is the difference between a Node.js server that handles 10,000 concurrent connections and one that freezes because you put a heavy for loop in the main thread.

If you block the Call Stack with synchronous heavy computation, nothing else runs. No API responses. No timers. No database callbacks. Everything waits.

That’s why you’ll hear people say “Don’t block the Event Loop.” It’s not a suggestion. It’s survival advice.

The Number That Blows My Mind

Node.js, powered by the Event Loop, can handle roughly 1 million concurrent connections on a single thread with proper async code. Apache starts struggling at around 10,000. That’s a 100x difference — not from more hardware, but from smarter architecture.


JavaScript doesn’t multitask. It just manages its to-do list better than most of us do.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top