Node.jsLesson 2 of 13

The Event Loop Explained

The event loop is the engine that lets one Node.js process handle thousands of visitors at once. It sounds technical, but the idea is simple. Node.js runs your JavaScript on a single thread, so it can literally do one thing at a time. The trick is that most work is not done by JavaScript at all.

The event loop is the engine that lets one Node.js process handle thousands of visitors at once. It sounds technical, but the idea is simple. Node.js runs your JavaScript on a single thread, so it can literally do one thing at a time. The trick is that most work is not done by JavaScript at all.

When your code asks to read a file, query a database, or wait on a timer, Node.js hands that job to the operating system and moves on to the next task. The event loop keeps circling, checking whether any of those finished jobs are ready. When one is, it runs the callback you attached to it. That is the whole machine: start work, keep moving, run callbacks when work completes.

This is why synchronous code is dangerous. A synchronous file read or a long loop runs directly on the main thread and blocks everything behind it. Every other visitor waits, timers stall, and the server appears frozen. Keeping slow work asynchronous is the single most important habit for Node.js code.

One thread, many jobs

The event loop never stops, and it never runs your callbacks at the same time. Work that is slow, like file I/O, happens in the background through the operating system. When it finishes, the callback is queued, and the loop runs it on the next pass. That is how one thread seems to do many things at once.

JS
setTimeout(() => {
  console.log("timer done"); // runs later, after the loop frees up
}, 1000);

console.log("first"); // runs immediately
// output: first, then timer done

What blocks the event loop

Anything that runs synchronously on the main thread blocks the loop: fs.readFileSync, a huge JSON.parse, or a loop that counts to a billion. While it runs, no timers fire and no responses are sent. The fix is to use the async versions, like fs.promises, and avoid heavy CPU work on the main thread.

JS
// This freezes every visitor for the duration:
const data = fs.readFileSync("big.txt", "utf8");

// This does not block:
const data = await fs.promises.readFile("big.txt", "utf8");

Timers and the loop

setTimeout and setInterval are handled by the loop, not by JavaScript itself. The delay is the minimum wait, not an exact time. If the main thread is busy, the callback runs late. Never assume a timer fires on time; assume it fires after the given delay at the earliest.

JS
const start = Date.now();
setTimeout(() => {
  console.log("elapsed:", Date.now() - start);
}, 500);
// will print 500 or more, never less

Syntax

JS
const fs = require("fs");

// This blocks the event loop while the disk works:
// const data = fs.readFileSync("notes.txt", "utf8");

// This lets the loop keep serving other requests:
fs.promises.readFile("notes.txt", "utf8").then((data) => {
  console.log(data);
});

console.log("loop is still free"); // runs before the file is read

setTimeout 0 Still Waits for I/O

setTimeout 0 runs after I/O, not immediately — event loop phases matter.

JS
setTimeout(()=>console.log(2),0); fs.readFile(()=>console.log(1)) // 1 then 2

Good to know

  • The event loop runs one JavaScript task at a time on a single thread.
  • Slow work is handed to the operating system and picked up later.
  • Synchronous file reads and long loops block every other request.
  • Timer callbacks run at the earliest possible time, never exactly on time.
Try it Yourself Edit the code, press Run, then check what changed in the Result.
Ready
Code
Result

Practice makes it stick

Open the Playground and build your own page with the tags you just learned.

→ Open Playground