I spent a week reading about the Node.js Event Loop. This is everything I found out
So my mentor challenged me to study about the Event Loop before our next meeting and I realised while we learn about the Event Loop as a theoretical concept, very few understand how it works in practice. We’re all taught that Javascript is single threaded, meanwhile it can handle asynchronous operations like API calls, timers, and file reading without blocking execution. At first, this feels contradictory. How can a language that runs only one operation at a time manage multiple tasks efficiently? This is the core idea behind the event loop. To understand how the event loop works, we first need to look at where JavaScript actually executes code: the call stack. The call stack is where JavaScript keeps track of function execution. It determines which function is currently running and what should run next. You can think of it as a stack of books. Whenever a function is called, it is added (or “pushed”) onto the stack. When that function finishes execution, it is removed (or “popped”) from the stack. Because of this structure, the call stack follows a Last In, First Out (LIFO) order; the most recently added function is the first to be executed and removed. This model explains how JavaScript executes code synchronously: each function runs to completion before the next one begins. However, this raises an important question: what happens when a task takes time to complete, like fetching data from an API or waiting on a timer? If such tasks were handled directly on the call stack, they would block everything else from running. To avoid this, JavaScript relies on external APIs provided by its environment. In the browser, these are known as Web APIs (such as setTimeout, DOM events, fetch, etc). In Node.js, similar functionality is provided by built-in APIs for tasks like fs operations, networking, timers, etc. When an asynchronous function is called, it is handed off to these APIs instead of remaining on the call stack. This allows the call stack to stay free and continue executing other code. Once the operation is complete, its callback function is not executed immediately. Instead, it is placed in a queue, waiting for the right moment to be processed. This queue is known as the callback queue (or task queue), and it holds callbacks from completed asynchronous operations in the order they were received. Now this is where the event loop comes in. The event loop continuously monitors the call stack. When the stack becomes empty, it takes a task from a queue and pushes it onto the stack for execution. At this point, it might seem like the event loop simply processes tasks from a single queue in order. However, this is not the full picture. Consider the following code: You might expect the setTimeout callback to run first since it appears earlier in the code. But when we run this we might be in for a little surprise: So why does the Promise execute before the timeout? The reason is that the event loop does not rely on just one queue. Instead, it prioritizes tasks using two main queues: the microtask queue and the macrotask queue. Microtask Queue (Higher Priority) The microtask queue holds tasks such as: Promise callbacks (.then(), .catch(), .finally()) queueMicrotask In Node.js, process.nextTick (with even higher priority) Microtasks are executed immediately after the current operation completes and before the event loop moves to the next Macrotask. Macrotask Queue (Lower Priority) The macrotask queue (or callback queue) contains tasks like: setTimeout and setInterval I/O operations (e.g., fs or network requests) setImmediate (in Node.js) These tasks are processed only after all microtasks have been completed. So In our example, the Promise callback is placed in the microtask queue, while the setTimeout callback goes into the macrotask queue. When the call stack becomes empty, the event loop processes all tasks in the microtask queue, then moves to the macrotask queue. This is why the Promise runs before the timeout, even though it appears later in the code. Conclusion The event loop is what allows JavaScript to handle asynchronous operations while still being single-threaded. Rather than executing everything directly on the call stack, JavaScript delegates time-consuming tasks to external APIs, queues their callbacks, and uses the event loop to decide when they should run. However, not all tasks are treated equally. Microtasks are always processed before macrotasks, which is why, for example, Promise callbacks run before functions scheduled with setTimeout, even if they appear later in the code. Understanding this execution order removes much of the confusion around asynchronous JavaScript. It explains why certain operations behave the way they do, and the implications this behaviour has in software development.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to