How async code flows through Node.js
flowchart LR
A["<b>i · Call stack</b><br/>V8 runs sync code<br/>GEC + function frames"]
-->|"sync"| D["<b>vi · console.log</b><br/>output printed<br/>stack drained"]
A -->|"async call<br/>(fs / http / timer)"| B["<b>ii · libuv</b><br/>registers callback<br/>uses thread pool + OS"]
B -->|"OS does the work"| C["<b>iii · OS</b><br/>disk · network · timer<br/>(libuv talks to kernel)"]
C -->|"result ready"| B
B -->|"push callback"| Q["<b>iv · Task queues</b><br/>macrotasks · microtasks"]
Q -->|"event loop checks<br/>'is stack empty?'"| E["<b>v · Event loop</b><br/>moves callback<br/>onto the call stack"]
E --> A
classDef v8 fill:#fdecd3,stroke:#c2410c,stroke-width:2px,color:#1a1915;
classDef libuv fill:#e7efd9,stroke:#587640,stroke-width:2px,color:#1a1915;
classDef os fill:#eaf2f8,stroke:#3a6ea5,stroke-width:2px,color:#1a1915;
classDef queue fill:#ece5f5,stroke:#6b46c1,stroke-width:2px,color:#1a1915;
classDef out fill:#f5efe1,stroke:#6a8a4f,stroke-width:2px,color:#1a1915;
class A,D v8
class B libuv
class C os
class Q queue
class E out-
Call stack (V8)
V8 executes synchronous code one frame at a time — GEC plus any active function contexts.
-
libuv
C library bundled with Node. Registers async ops with their callbacks and uses the thread pool + OS syscalls to do the work.
-
Operating system
Disk, network, timers. libuv is the only part of Node that can talk to the kernel.
-
Task queues
Macrotasks (timers, I/O callbacks) and microtasks (promises, queueMicrotask). Microtasks drain first.
-
Event loop
Checks whether the call stack is empty; if yes, moves the next ready callback onto it.
-
Output
Sync output prints immediately; async output prints only after libuv + the event loop hand the callback back to V8.
Comments
Comments are disabled in this environment. Set
PUBLIC_GISCUS_REPO,PUBLIC_GISCUS_REPO_ID, andPUBLIC_GISCUS_CATEGORY_IDto enable.