Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Introduction to Node.js
What is Node.js?
Node.js is an open-source, cross-platform JavaScript runtime environment that executes JavaScript code outside a web browser. It allows developers to use JavaScript to write command-line tools and for server-side scripting—running scripts server-side to produce dynamic web page content before the page is sent to the user’s web browser. Think of it this way: JavaScript was born inside the browser, confined to making web pages interactive. Node.js broke it out of that cage. Ryan Dahl looked at how web servers handled connections in 2009—most used a “one thread per connection” model that buckled under load—and realized that JavaScript’s event-driven nature was actually perfect for building servers that could handle thousands of simultaneous connections without breaking a sweat.Key Features
| Feature | Description |
|---|---|
| Asynchronous & Event-Driven | All APIs are non-blocking. The server never waits for an API to return data. |
| Single-Threaded | Uses event looping for high scalability without thread management overhead. |
| Fast Execution | Built on Chrome’s V8 engine, compiling JavaScript directly to machine code. |
| No Buffering | Outputs data in chunks, enabling efficient streaming. |
| Rich Ecosystem | Over 2 million packages available via NPM. |
| Cross-Platform | Runs on Windows, macOS, Linux, and more. |
When to Use Node.js
✅ Ideal for:- Real-time applications (chat, gaming, collaboration tools)
- API servers and microservices
- Streaming applications
- Single Page Application (SPA) backends
- CLI tools and scripts
- IoT applications
- CPU-intensive computations (video encoding, machine learning)
- Applications requiring multi-threading by design
- Monolithic enterprise applications with heavy business logic
Node.js vs Other Backend Runtimes
| Dimension | Node.js | Python (Django/Flask) | Go | Java (Spring) |
|---|---|---|---|---|
| Concurrency model | Single-threaded event loop | Multi-threaded (GIL limits CPU) | Goroutines (lightweight threads) | OS threads with thread pool |
| I/O throughput | Excellent — non-blocking by default | Good with async (ASGI), limited in sync mode | Excellent — goroutines are cheap | Good with reactive (WebFlux) |
| CPU-bound work | Poor without Worker Threads | Moderate (C extensions help) | Excellent — compiled, multi-core | Excellent — JIT compiled |
| Cold start time | ~50-100ms | ~200-500ms | ~5-10ms | ~1-5 seconds |
| Memory per instance | ~30-50MB baseline | ~40-80MB baseline | ~5-15MB baseline | ~100-200MB baseline |
| Ecosystem size | 2M+ NPM packages | 400K+ PyPI packages | 1M+ Go modules | 500K+ Maven artifacts |
| Best fit | I/O-heavy APIs, real-time | Data science, scripting, web apps | Systems, CLIs, high-perf APIs | Enterprise, Android, large teams |
- Your team already knows JavaScript and you want a single language across frontend and backend.
- Your workload is I/O-bound (database queries, HTTP calls, file operations) rather than CPU-bound.
- You need real-time features (WebSockets, SSE) and want them as first-class citizens.
- Startup time matters — serverless functions, CLI tools, or dev tooling.
The V8 Engine
At the core of Node.js is the V8 JavaScript engine, the same engine that powers Google Chrome. V8 compiles JavaScript directly to native machine code using Just-In-Time (JIT) compilation, resulting in exceptional performance.How V8 Works
Key V8 Optimizations
- Hidden Classes: V8 creates hidden classes for objects to optimize property access. This is why initializing all object properties in the constructor (rather than adding them dynamically later) leads to faster code—V8 can share the same hidden class across all instances.
- Inline Caching: Remembers where to find object properties so repeated access is nearly as fast as C++ struct access.
- Garbage Collection: Automatic memory management with generational GC. Most objects are short-lived, so V8 uses a “nursery” (young generation) that is collected quickly and cheaply. Long-lived objects get promoted to the “old generation” which is collected less frequently. Understanding this matters when debugging memory leaks—tools like
--inspectand Chrome DevTools heap snapshots let you see which objects are being retained.
V8 Performance: What the Numbers Look Like
These are rough benchmarks to build intuition, not absolute truths (hardware, Node version, and workload all matter):| Operation | Approximate Throughput | Notes |
|---|---|---|
| Property access (monomorphic) | ~1 billion/sec | V8 optimizes single-shape objects aggressively |
| Property access (megamorphic) | ~50-100 million/sec | Objects with many shapes defeat inline caching |
| JSON.parse (1KB payload) | ~500,000/sec | Faster than manual parsing for structured data |
| JSON.stringify (1KB object) | ~300,000/sec | Major cost in high-throughput APIs — consider caching |
| Object creation (small) | ~100 million/sec | Young generation GC handles short-lived objects efficiently |
| RegExp match (simple) | ~10-50 million/sec | Compiled to native code after warmup |
{a:1, b:2} vs {b:2, a:1}) forces V8 to create separate hidden classes for each shape, which defeats inline caching and can cause 10-20x slowdowns in hot loops. This is why database rows returned from ORMs are usually fast to access — they all share the same shape.
Installation
To get started, you need to install Node.js on your machine.Windows / macOS / Linux
- Visit the official Node.js website.
- Download the LTS (Long Term Support) version. This version is recommended for most users as it’s the most stable.
- Run the installer and follow the on-screen instructions.
Verifying Installation
Open your terminal or command prompt and run the following commands:The REPL (Read-Eval-Print Loop)
Node.js comes with a built-in REPL environment. It allows you to execute JavaScript code directly in the terminal. To enter the REPL, simply typenode in your terminal:
Ctrl + C twice to exit the REPL.
Your First Node.js Script
Let’s create a simple file to run with Node.js.- Create a file named
app.js. - Add the following code:
- Run the file using the
nodecommand:
Global Objects
In the browser, the global object iswindow. In Node.js, the global object is global.
document or window do not exist in Node.js.
The Event Loop
Understanding the Event Loop is crucial to mastering Node.js. It’s the mechanism that allows Node.js to perform non-blocking I/O operations despite JavaScript being single-threaded. The Restaurant Host Analogy: Imagine a restaurant with a single host (the event loop). When a guest arrives, the host doesn’t cook the meal personally—they seat the guest, hand the order to the kitchen (the OS or thread pool), and immediately greet the next guest. When the kitchen rings the bell (a callback), the host delivers the food. One host can manage dozens of tables this way. A traditional multi-threaded server is like hiring a personal waiter for every single guest—it works, but you run out of waiters fast.Event Loop Phases
Async Scheduling: Decision Framework
Choosing betweensetTimeout, setImmediate, process.nextTick, and queueMicrotask is a common source of confusion. Use this decision table:
| Mechanism | When it runs | Use when you need to… | Watch out for |
|---|---|---|---|
process.nextTick() | Before any I/O, before promises | Ensure callback runs before event loop continues (e.g., emitting events after constructor returns) | Recursive calls starve I/O indefinitely |
queueMicrotask() | After nextTick, before I/O | Standard microtask scheduling (same as resolved promises) | Same starvation risk as nextTick |
setTimeout(fn, 0) | Next iteration, timers phase | Defer work to a future event loop tick with minimum 1ms delay | Minimum delay is 1ms (not 0), clamped to 4ms after 5 nested calls in browsers |
setImmediate() | Current iteration, check phase (after I/O poll) | Yield to I/O between iterations of a loop | Only available in Node.js, not browsers |
fs.readFile), setImmediate always fires before setTimeout(fn, 0). Outside I/O callbacks, their order is non-deterministic and depends on the event loop’s timing. If your code relies on a specific order between these two, your code has a bug.
Example: Understanding Execution Order
The Process Object
Theprocess object is a global that provides information about, and control over, the current Node.js process.
Common Process Properties
Process Events
Advanced Setup Options
Using NVM (Node Version Manager)
Managing multiple Node.js versions is essential for professional development. macOS/Linux:Project Structure Best Practices
Summary
- Node.js is a JavaScript runtime built on Chrome’s V8 engine
- The Event Loop enables non-blocking I/O with a single thread
- The
processobject provides control over the Node.js process - Use NVM to manage multiple Node.js versions
- Understand the Event Loop phases for better async code
- Follow consistent project structure patterns
Interview Deep-Dive
Walk me through the Node.js event loop phases in order. What happens when you call setTimeout(fn, 0) versus setImmediate(fn) inside an I/O callback?
Walk me through the Node.js event loop phases in order. What happens when you call setTimeout(fn, 0) versus setImmediate(fn) inside an I/O callback?
- Timers phase executes callbacks scheduled by
setTimeoutandsetIntervalwhose threshold has elapsed. - Pending callbacks phase handles deferred I/O callbacks from the previous cycle (e.g., TCP errors).
- Poll phase retrieves new I/O events and executes their callbacks. If there is nothing else scheduled, the loop will block here waiting for new I/O.
- Check phase executes
setImmediatecallbacks — this is the key distinction.
fs.readFile), setImmediate always fires before setTimeout(fn, 0) because after the poll phase completes, the check phase runs next, while setTimeout(fn, 0) must wait for the next iteration to reach the timers phase.Outside an I/O callback, the order between setTimeout(fn, 0) and setImmediate is non-deterministic because it depends on whether the event loop has entered the timers phase before the 1ms minimum delay of setTimeout has elapsed. If your application logic depends on a specific order between these two outside I/O, that is a bug.Before any phase runs, Node.js drains the microtask queue: first all process.nextTick callbacks, then all resolved promise callbacks. This means process.nextTick and queueMicrotask always execute before the event loop moves to its next phase — and recursive nextTick calls can starve I/O indefinitely.In production, I have seen a recursive process.nextTick pattern completely freeze a server’s ability to process incoming HTTP requests. We switched to setImmediate for the recursive callback and the issue resolved immediately because setImmediate yields to the event loop between calls.Follow-up: You mentioned process.nextTick can starve I/O. How would you detect that starvation is happening in a running production server?You would monitor event loop lag — the difference between when a timer was scheduled to fire and when it actually fires. Libraries like monitorEventLoopDelay (built into Node.js perf_hooks since v11) or packages like event-loop-stats expose this metric. If your event loop lag spikes from sub-millisecond to hundreds of milliseconds, something is blocking or starving the loop. You can also use --prof to generate a V8 CPU profile and inspect where time is being spent. In practice, we set up a setInterval health check that logs a timestamp every second; if gaps appear in the log, the event loop was blocked during that window.Explain V8's hidden classes and inline caching. How can a developer accidentally defeat these optimizations?
Explain V8's hidden classes and inline caching. How can a developer accidentally defeat these optimizations?
Your Node.js API starts returning 503s under moderate load even though CPU usage is only 15%. What is your debugging approach?
Your Node.js API starts returning 503s under moderate load even though CPU usage is only 15%. What is your debugging approach?
-
Check event loop lag. Use
perf_hooks.monitorEventLoopDelay()or a lightweight interval check. If lag is in the hundreds of milliseconds, something is blocking the loop. - Check connection pools. If the database pool is exhausted (all connections are checked out and waiting), incoming requests queue up and eventually timeout. I would check the pool’s active/waiting count. Same for Redis connections.
-
Check for file descriptor exhaustion. Each open socket, file handle, and pipe consumes a file descriptor. On Linux,
ulimit -nshows the limit (often 1024 by default). Under load, you can hit this silently and new connections fail.lsof -p <pid> | wc -ltells you the current count. - Check for DNS resolution delays. If every outbound request triggers a DNS lookup and the DNS server is slow, you get cascading latency. This is a common surprise in containerized environments.
-
Take a CPU profile. Connect Chrome DevTools via
--inspect, record a profile under load, and look for synchronous functions consuming disproportionate time — oftenJSON.parseon large payloads, synchronous file reads, or regex backtracking. -
Check memory. If the process is near the heap limit, V8 garbage collection pauses can freeze the event loop for hundreds of milliseconds.
process.memoryUsage()or a monitoring tool like Prometheus withprom-clientwill reveal this.
.lean() on a large result set, causing Mongoose to hydrate 5,000 full document objects per request. The GC pressure from creating and discarding those objects caused event loop stalls. Adding .lean() dropped response time from 800ms to 40ms.Follow-up: How do you instrument a Node.js service so these problems surface in monitoring before users complain?Export event loop lag, heap usage, active handles/requests, and connection pool saturation as Prometheus metrics using prom-client. Set alerts on event loop lag exceeding 100ms, heap usage above 80%, and connection pool wait time. For the event loop specifically, Node.js has process._getActiveHandles().length and process._getActiveRequests().length which reveal resource leaks. Combine with distributed tracing (OpenTelemetry) so you can see which specific code path is slow.What is the difference between process.nextTick, queueMicrotask, and Promise.resolve().then()? When would you use each one?
What is the difference between process.nextTick, queueMicrotask, and Promise.resolve().then()? When would you use each one?
-
process.nextTick runs first — before any microtask. Node.js drains the entire nextTick queue before moving on. It exists because early Node.js needed a way to emit events after a constructor returns but before any I/O, ensuring listeners registered synchronously would be called. The classic use case:
EventEmittersubclasses that need to emit an event in the constructor. If you emit synchronously, no listeners are attached yet. If you usenextTick, listeners can be attached on the same tick before the emission runs. - queueMicrotask runs after nextTick callbacks, in the standard microtask queue (the same queue that resolved promises use). It was added to Node.js to align with the browser-standard microtask API. Use it when you need standard microtask timing without the Node-specific priority of nextTick.
-
Promise.resolve().then() also queues into the microtask queue, effectively the same timing as
queueMicrotask. The difference is purely ergonomic —queueMicrotaskis a direct API call without creating a throwaway Promise object.
process.nextTick only when you specifically need to run before pending microtasks (the EventEmitter constructor pattern). For everything else, prefer queueMicrotask or just use promises naturally. Never use recursive process.nextTick — it starves I/O because Node.js drains the entire nextTick queue before checking for I/O. Recursive setImmediate is the safe alternative because it yields to the event loop between calls.Follow-up: Can you give an example where using process.nextTick instead of setImmediate caused a real production issue?A common pattern is a streaming parser that processes incoming data and emits parsed events. If the parser uses process.nextTick to emit each parsed chunk, and the input arrives faster than the consumer can process, the nextTick queue fills up and I/O callbacks (including the ‘data’ events that bring in new chunks) never execute. The process appears frozen even though it is working — just working on nextTick callbacks forever. The fix is to use setImmediate so each iteration yields to the event loop, allowing I/O to interleave with processing. This exact issue has appeared in popular libraries like JSONStream in certain edge cases.