Node.js Fundamentals & Architecture

Difficulty

Answer: Node.js is a JavaScript runtime built on Chrome's V8 engine that lets you run JavaScript on the server, outside a browser.

The main pieces:

  1. V8 — Google's engine that parses and JIT-compiles JavaScript to native machine code. It provides the language itself (objects, functions, GC) but knows nothing about files, networks, or timers.
  2. libuv — a C library that provides the event loop, a thread pool, and cross-platform asynchronous I/O (file system, DNS, networking). This is what makes non-blocking I/O possible.
  3. Node bindings / C++ core — glue that exposes libuv and other native capabilities to JavaScript as built-in modules (fs, net, http, crypto, ...).
  4. Standard library — the JavaScript-level built-in modules you require/import.

How a request flows:

const fs = require('fs');

// JS asks Node to read a file and hands it a callback.
fs.readFile('data.txt', (err, data) => {
  console.log('done reading');
});
console.log('this logs first');
  • The readFile call is handed off to libuv, which performs the actual disk I/O on a background thread.
  • The main thread keeps running JavaScript (so 'this logs first' prints before 'done reading').
  • When the I/O finishes, libuv queues the callback, and the event loop runs it when the call stack is empty.

Interview tip: Be clear that V8 runs the JavaScript and libuv provides the asynchronous, event-driven I/O — Node is the combination, not just "JavaScript on the server."

Answer: Node runs your JavaScript on one thread, but achieves high concurrency by never waiting on that thread.

Single-threaded (for your code):

  • There is one call stack executing your JavaScript. You never deal with data races or locks in your own logic because only one piece of JS runs at a time.

Non-blocking I/O:

  • Instead of blocking the thread while reading a file or querying a database, Node hands the operation to libuv, which uses the OS's async facilities (epoll, kqueue, IOCP) or its thread pool.
  • The main thread immediately continues to the next line. When the operation completes, its callback is placed on a queue and the event loop runs it when the stack is idle.
// Blocking (bad on the main thread) — nothing else runs during the read
const data = fs.readFileSync('big.log');

// Non-blocking — the thread is free to do other work meanwhile
fs.readFile('big.log', (err, data) => { /* ... */ });

So where does concurrency come from?

  • I/O concurrency: many sockets/files can be "in flight" at once because the OS/libuv handle them; Node just reacts to completions.
  • CPU work still blocks the single thread. For CPU-bound tasks you must use worker_threads, the cluster module, or child processes.

The trade-off: Node excels at I/O-bound workloads (APIs, proxies, real-time apps) with low memory overhead per connection, but a single long synchronous computation (e.g., a tight loop or heavy JSON.parse) blocks everything.

Answer: Both run JavaScript on V8, but the host environment and available APIs differ.

BrowserNode.js
Global objectwindow / selfglobal / globalThis
UI / DOMdocument, DOM APIsnone
File systemno direct accessfs, path, os
Networkingfetch, XMLHttpRequest, WebSockethttp, net, dgram (+ fetch in modern Node)
ModulesES Modules (native)CommonJS and ES Modules
Binary dataArrayBuffer, BlobBuffer (plus ArrayBuffer)
Process infolimitedprocess (env, argv, cwd, signals)
Security modelsandboxed per-originfull OS access with the user's permissions

Key implications:

  • Code that touches the DOM won't run in Node; code that touches fs/process won't run in the browser.
  • Node has no same-origin sandbox — a Node script can read your files and open sockets, so supply-chain security (what you npm install) matters.
  • Isomorphic/universal code (shared between client and server) must avoid environment-specific globals or guard for them.

Answer: process is a global object that represents the running Node process and lets you interact with the OS-level environment.

Environment variables — process.env:

// Read config from the environment (12-factor style)
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;

Values are always strings (or undefined). Use them for secrets and per-environment config instead of hardcoding.

Command-line arguments — process.argv:

// node app.js --name Alice
console.log(process.argv);
// [ '/path/to/node', '/path/to/app.js', '--name', 'Alice' ]
const args = process.argv.slice(2); // just your args

Other commonly used members:

  • process.cwd() — current working directory.
  • process.platform / process.arch — OS and CPU architecture.
  • process.pid — process id.
  • process.exit(code) — exit immediately with a status code (avoid unless necessary; prefer letting the event loop drain).
  • process.nextTick(cb) — run a callback before the next event-loop phase.
  • process.memoryUsage() — heap/RSS stats.

Lifecycle events (useful for graceful shutdown):

process.on('SIGINT', () => { /* Ctrl+C: close servers, DB, then exit */ });
process.on('SIGTERM', () => { /* container stop */ });
process.on('uncaughtException', (err) => { /* log, then exit */ });
process.on('unhandledRejection', (reason) => { /* log */ });

Answer:

global — Node's global namespace object, analogous to window in the browser. Properties attached to it are visible everywhere:

global.myShared = 42;   // accessible as myShared anywhere (generally avoid this)

globalThis — the ES2020 standard way to reference the global object regardless of environment. In Node it is global; in the browser it's window. Prefer globalThis for portable code.

process — a specific global object (not the global scope itself) describing the current process; see environment variables, argv, signals, etc.

Globals you can use without require:

  • console, setTimeout / setInterval / setImmediate, queueMicrotask
  • Buffer, TextEncoder/TextDecoder, URL, fetch (modern Node)
  • __dirname, __filename, require, module, exports (in CommonJS)

Important subtlety — module scope vs global scope:

// In a CommonJS file:
const x = 1;      // module-scoped, NOT a property of global
global.y = 2;     // truly global

console.log(global.x); // undefined
console.log(global.y); // 2

Each file is wrapped in a function by Node, so top-level const/let/var stay local to that module. This is why one file's variables don't leak into another — a common point of confusion for people coming from browser <script> tags where top-level var is global.

Note: __dirname and __filename exist in CommonJS but not in ES Modules, where you derive them from import.meta.url.