Node runs JavaScript outside a browser, on one thread, with a loop that hands slow work to the operating system and comes back for the answer. Almost every mysterious Node bug traces to one of three things: something occupied that single thread, an error crossed an asynchronous boundary where try could not follow, or a file resolved by rules nobody had read. This is the shape of the runtime — what runs when, what stalls, how failure travels, and why the two module systems disagree.
Each chapter opens with the short version. Tap one to read the detail.
One thread, and what it costs
~2 min
Your JavaScript runs on one thread, so anything synchronous delays every pending request and connection. Node also ships globals you never import — and names that only look global.
The loop: phase order, and the queues that jump it
~2 min
Each turn runs a fixed sequence — timers, poll for I/O, then check for setImmediate. Two queues jump all of it: process.nextTick and promise microtasks, drained to empty before the loop moves on.
Launching, configuring and stopping a process
~2 min
Your arguments start at index 2, every value in process.env is a string (so "false" is truthy), and runtime flags only work before the entry point. Prefer process.exitCode to process.exit().
CommonJS: the cache, the wrapper, and cycles
~2 min
require evaluates a file on the spot, blocking the loop, then caches it by resolved path so the body runs once. The classic bug is reassigning exports, which rebinds a local name and exports nothing.
ES modules: exact specifiers, and the CommonJS seam
~2 min
The resolver never guesses: extensions are mandatory and a directory has no implicit index. Importing CommonJS works, but its named exports come from a static scan, so anything assigned dynamically hides behind the default.
Failure: what try cannot catch
~2 min
A try block has already returned by the time an asynchronous callback throws, so the error escapes. Match on err.code, never message text — and an unlistened 'error' event crashes the process.
Bytes, chunks and backpressure
~2 min
A stream hands you arbitrary byte chunks, never records — a multi-byte character can split across two. write() returning false means stop until 'drain', and a failing pipe() leaves its destination open where pipeline() does not.
Files and paths
~2 min
readFile loads the whole file into memory and returns a Buffer unless you ask for an encoding. Path functions never touch the disk, and join will normalize .. straight out of your directory.
HTTP servers: nothing is parsed for you
~2 min
node:http handles message framing and no more — the body arrives as chunks you consume yourself, header names come back lower-cased, and a response you never end holds its socket until a timeout.
Getting work off the one thread
~2 min
Threads help with computation only — I/O is already concurrent, so a worker adds nothing there. Messages are copied by structured clone, which silently drops functions, getters and class identity.
Secrets, and shrinking the blast radius
~2 min
Hashing is one-way, so passwords need a work-factored derivation with a unique salt, not a bare digest. Compare secrets with timingSafeEqual — which throws rather than returning false on a length mismatch.
Measuring: the clock, and the loop
~2 min
Use the monotonic clock for durations — a wall-clock difference can be adjusted underneath you. The number that predicts user-visible latency is event-loop delay, reported as a histogram in nanoseconds.
Running TypeScript, and testing, with nothing installed
~2 min
Node runs a .ts file by erasing the annotations — it never type-checks, so a real checker still belongs in your pipeline. In node --test, a subtest you forget to await is cancelled and counted as a failure.
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.