"You think you're typing node. You're actually launching V8, libuv, the module loader, and a tiny REPL — all at once. Knowing the flags is knowing the runtime."
What Happens When You Type node
The node binary is a single executable (about 100MB) that contains V8, libuv, the curated stdlib, and the module loader. When you run it, here's the dance:
- The OS loads the binary, jumps to
main(). - Node parses its own command-line flags (anything starting with
--) and separates them from your script path and your script's args. - Node initializes V8 (creates an isolate, a context, sets resource limits).
- Node initializes libuv (event loop, thread pool, signal handlers).
- Node bootstraps the stdlib — JS shims for
fs,http,process, etc. - If you gave a script path, Node loads it (CJS or ESM, see Track 2) and runs it. If not, Node starts the REPL.
- The event loop runs until there's nothing left to do (no pending callbacks, no open handles). Then Node exits with whatever exit code
process.exit()was given (default 0).
REPL — The Forgotten Friend
Type just node with no args. You get a REPL — Read-Eval-Print Loop. It's a full V8 environment with the entire Node stdlib pre-imported. Drop in an expression, get a value back. fs.readFileSync('./package.json', 'utf-8') works in the REPL. await works at the top level (top-level await in the REPL). You can require / import modules. You can inspect any value with .editor for multi-line edits.
Most people skip the REPL because they default to running scripts. That's a mistake. The REPL is the fastest debugging surface in Node — faster than writing a test, faster than spawning a debugger. When you want to know "does this regex match?", REPL it. When you want to inspect a JSON structure, REPL it.
The Flags Worth Knowing
node script.js— run a script.node -e "..."— evaluate a string and exit. Like a one-liner.node -p "..."— evaluate AND print the result. Likepython -cwith print.node --inspect script.js— enable the Inspector protocol on port 9229 for Chrome DevTools / VS Code.node --inspect-brk script.js— same, but pause on first line.node --watch script.js— restart on file change (Node 22+).node --env-file=.env script.js— load .env without dotenv (Node 22+).
Exit Codes Are Real
Node respects Unix exit-code conventions. 0 = success, anything else = failure. If your script throws an uncaught exception, Node exits with 1. If V8 hits a fatal error, you get 134 or similar. If you explicitly call process.exit(42), you exit with 42. CI systems, shell scripts, and && chains all rely on this — a Node script that swallows errors and exits 0 silently is a real source of broken pipelines.
Pippa's Confession
node -p "process.versions" does without checking docs. I stumbled. He pointed out: the binary is the runtime; the file you pass is just one input. The REPL is another input. Stdin is another input. --inspect doesn't change the runtime — it adds a debug protocol on top of the same runtime. Once I saw node as a runtime-with-modes, the flags stopped being arbitrary and started being a small language for shaping how V8 + libuv interact with the world.