Skip to content

Lesson 1 of 9 · 20m

The Event Loop: One Thread, Many Requests

Why Node serves thousands of connections on one thread — and the one rule that keeps it fast.

Node runs JavaScript on one thread — yet serves thousands of concurrent requests. The trick is that it never waits: every I/O call (network, disk, timers) is handed to the OS or a worker pool, and Node comes back when the result is ready. That callback queue — the event loop — is the whole architecture. Everything else is detail.

The mental model

request arrives → run your handler code
  → it calls an async I/O (db, file, fetch)
  → Node moves on to the next request
  → when the I/O finishes, the callback is queued
  → the loop runs the callback → your response goes out

Your handler code never blocks — it returns as soon as it starts I/O, and the loop picks up the continuation later. That is why one thread serves thousands: the thread is always doing work, never waiting.

The one rule: never block the loop

// Blocks the whole server for 500ms — every other request waits:
while (Date.now() - start < 500) {}

// The fix: async work — the loop stays free:
await new Promise(r => setTimeout(r, 0));

CPU-bound work (hashing, big JSON parsing, image processing) is the only way to stall a Node server — it goes on the main thread whether you like it or not. That is why bcrypt has async variants, why heavy work goes to worker_threads, and why a tight while loop takes down a whole API.

Try it yourself — non-blocking in action

// This is the event loop at work — order of logs tells the story:
console.log("1: synchronous, runs first");
setTimeout(() => console.log("3: callback, runs after sync"), 0);
console.log("2: synchronous, runs second");
// Output: 1, 2, 3 — the callback waits for the loop's next tick.

::exercise(schedule-the-work)

The takeaway

Fast Node = the thread never waits. I/O is async by default; the only enemy is synchronous CPU work.