You've seen the event loop mentioned as the mechanism behind Node.js's non-blocking behavior. This page looks at it more directly — what it actually does, phase by phase, and why the order code runs in sometimes surprises people.
The core idea, restated simply
Node.js runs your JavaScript on a single thread. The event loop is what lets that one thread stay busy and responsive: it constantly checks whether any pending asynchronous work has finished, and if so, runs the associated callback — cycling through this check forever, for as long as the program is alive.
A simple ordering experiment
console.log('1: first line');
setTimeout(() => {
console.log('2: from setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('3: from a resolved Promise');
});
console.log('4: last line');
The output is not top-to-bottom — it's:
1: first line
4: last line
3: from a resolved Promise
2: from setTimeout
All synchronous code (1 and 4) runs first, in order, before the event loop even considers anything else. Then, Node handles what's called the microtask queue — Promise callbacks — before moving to the macrotask queue, which is where setTimeout callbacks live, even with a delay of 0. That's why the Promise callback (3) beats the setTimeout callback (2), despite setTimeout appearing earlier in the code.
A simplified mental map of the phases
The real event loop has several named phases, but the practical takeaway boils down to a priority order:
- Run all synchronous code in the current script, top to bottom.
- Drain the microtask queue — resolved/rejected Promise callbacks,
process.nextTick()callbacks (even higher priority than Promises). - Handle timers —
setTimeoutandsetIntervalcallbacks whose time has come. - Handle I/O callbacks — completed file reads, network responses, and similar.
- Handle
setImmediate()callbacks. - Loop back to step 2 and repeat, for as long as there's work left to do.
Why this actually matters for you
Understanding this ordering explains real bugs you'll eventually hit — like a Promise callback firing "too early" relative to a timer you expected to run first, or a process.nextTick() call unexpectedly starving the rest of the event loop if it's used recursively without care. You don't need to memorize every phase name, but knowing that synchronous code always finishes first, Promises come before timers, and I/O results arrive whenever the background work actually completes will save you real debugging time.
One line to hold onto
The event loop doesn't make Node.js faster at any single task — it makes Node.js good at never sitting idle while something is ready to be handled, which is exactly what high-concurrency servers need.