Unlike many other environments where each request might spawn a new thread, Node.js operates primarily on a single main thread. This sounds limiting, but it achieves high concurrency and performance through an **event-driven, non-blocking I/O model**, powered by the **Event Loop**.
Think of the Event Loop as a constantly running process that orchestrates tasks. When Node.js starts, it initializes the loop, processes the input script (which might register async operations), and then enters the loop. The loop's job is to check for pending events (like completed I/O, timers) and execute their associated callback functions.
The key is non-blocking I/O. Instead of waiting for slow operations (like reading a file or making a network request), Node.js delegates these tasks to the operating system or a worker thread pool (managed by the libuv library). When the operation completes, the OS/libuv notifies Node.js by placing the corresponding callback function into a queue. The Event Loop picks up these callbacks from the queue and executes them on the main thread when it gets a chance.
process.nextTick
callbacks (executed first within this queue) and resolved Promise callbacks (.then
, .catch
). Processed immediately after the current script/callback finishes and between event loop phases.setImmediate
, etc. Processed during specific Event Loop phases.The loop cycles through phases, processing specific queues in each:
setTimeout
/ setInterval
callbacks.setImmediate
callbacks.Crucially, after *each* callback from a macrotask queue (or the initial script), the *entire* Microtask Queue is processed before moving to the next callback or the next phase.
Add tasks and step through or run to see the execution flow and queue interactions.