Skip to content

Event Loop & Async

JavaScript in Node.js runs on a single thread. Yet it can handle thousands of simultaneous connections without blocking. The secret is the event loop — a mechanism that delegates slow work (file reads, network calls) to the operating system, then processes the results when they arrive.

At its core, the runtime has three key structures:

  • Call stack — where synchronous code executes, one frame at a time.
  • Microtask queue — holds Promise callbacks and queueMicrotask callbacks. Drains completely after every task.
  • Macrotask queue (task queue) — holds setTimeout/setInterval callbacks. One macrotask is processed per event loop iteration.

The loop runs continuously: pick a macrotask → execute it → drain all microtasks → pick the next macrotask.

LessonTopic
Call StackSynchronous execution and why blocking matters
CallbacksThe original async pattern and Node’s error-first convention
PromisesStates, chaining, and combinators
Async/AwaitReading async code like synchronous code
Microtasks & MacrotasksQueue priorities and drain order
TimerssetTimeout, setInterval, and timer precision

The snippet below runs three pieces of code. Notice they do not execute top-to-bottom in the order written — the synchronous log fires first, the Promise callback fires next (microtask), and the setTimeout callback fires last (macrotask).

// What runs first?
console.log('A — sync');
Promise.resolve().then(() => console.log('B — microtask (Promise)'));
setTimeout(() => console.log('C — macrotask (setTimeout)'), 0);

Run it to confirm the order:

JavaScript

The output is always A, then B, then C — the event loop guarantees this ordering.

In what order does the event loop process queues after executing a task?
Which of the following is placed in the microtask queue?