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:
- 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.
- 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.
- Node bindings / C++ core — glue that exposes libuv and other native capabilities to JavaScript as built-in modules (
fs,net,http,crypto, ...). - 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
readFilecall 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, theclustermodule, 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.
| Browser | Node.js | |
|---|---|---|
| Global object | window / self | global / globalThis |
| UI / DOM | document, DOM APIs | none |
| File system | no direct access | fs, path, os |
| Networking | fetch, XMLHttpRequest, WebSocket | http, net, dgram (+ fetch in modern Node) |
| Modules | ES Modules (native) | CommonJS and ES Modules |
| Binary data | ArrayBuffer, Blob | Buffer (plus ArrayBuffer) |
| Process info | limited | process (env, argv, cwd, signals) |
| Security model | sandboxed per-origin | full OS access with the user's permissions |
Key implications:
- Code that touches the DOM won't run in Node; code that touches
fs/processwon'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,queueMicrotaskBuffer,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.