Node.js Interview Questions: The Principal Architect Round
50 Highly Advanced, Production-tested Expert Node.js Interview Questions & Answers used by Technical Directors at Top MNCs.
Expert Level (Q1 – Q50)
V8 compiles JavaScript down to native machine code. Since JS is dynamically typed, V8 creates hidden internal classes (Shapes) under the hood to track property offsets. When an object changes its structure dynamically, V8 creates a new hidden class transition tree. Inline Caches store shortcuts for locating property locations within functions.
Event Loop Lag cannot be calculated natively via traditional CPU usage percentages. You must measure the delay between when a timer was scheduled to execute versus when it actually runs. This can be accurately tracked using Node’s native `perf_hooks` module via the `monitorEventLoopDelay` function.
Node’s native `dns.lookup` is a synchronous, blocking system call that utilizes `getaddrinfo()` at the operating system level. Because it is synchronous, libuv is forced to execute it inside the internal thread pool, completely exhausting the default pool of 4 threads and stalling all other file system or crypto tasks.
Backpressure management inside custom streams requires monitoring the return value of `.write()`. If `.write(chunk)` returns `false`, the internal `highWaterMark` threshold has been breached. You must halt reading operations immediately and wait for the destination stream to emit the `’drain’` event before resuming transmission.
To achieve maximum throughput without serialization overhead between workers, you use `SharedArrayBuffer`. It allocates memory outside the main V8 heap that can be shared across multiple threads directly. To avoid race conditions, you use the global `Atomics` object, which provides atomic thread-safe operations and wait/notify sync patterns.
You handle this by leveraging the native `v8` module’s `writeHeapSnapshot()` method inside an automated health monitor loop. You check the memory consumption using `process.memoryUsage().heapUsed` on a regular interval, and trigger a snapshot automatically if usage crosses a set limit.
Under heavy production load, running a debugger causes unacceptable performance penalties. Instead, you boot the application with the `–prof` flag to collect V8 execution samples or run profiling tools like `0x` which utilize system trace mechanisms to capture execution stacks and generate a visual Flame Graph.
`AsyncLocalStorage` relies on internal V8 hooks (`async_hooks`) to trace context continuity across execution contexts and event loops. Every asynchronous boundary crossing (e.g., an await statement) invokes a tracking hook, which incurs a small CPU runtime overhead depending on the nesting complexity.
Prototype Pollution occurs when user input modifies properties on `__proto__`. If an application uses an unvalidated deep-merge utility, an attacker can append global values. If the application later forks a child process using option configurations that fallback on defaults, the attacker can hijack execution arguments via polluted properties.
The Redlock algorithm requires acquiring locks across multiple independent Redis instances concurrently using matching random string validations. To handle clock drift between physical servers, the lock validity time must be adjusted downwards by subtracting a drift factor (a few milliseconds) to guarantee safe mutual exclusion.
Since microservices manage isolated databases, traditional ACID transactions across them are impossible. The Saga pattern designs a workflow as a series of distinct local transactions. Each step has a corresponding reverse “compensating transaction” that executes if any subsequent phase fails, ensuring data consistency.
By default, Node’s global HTTP Agent does not enforce tight configuration constraints under massive production loads. You must instantiate a custom `https.Agent` specifying parameters like `keepAlive: true`, `maxSockets: Infinity`, `maxFreeSockets: 256`, and align the `keepAliveMsecs` parameter to exactly match or fall below the timeout configuration of your downstream load balancer.
ECONNRESET errors when calling downstream internal microservices. The architect discovers the AWS Application Load Balancer has an idle timeout of 60 seconds, while Node’s default agent timeout is shorter. Aligning the connection configurations eliminates the socket drops.Executing synchronous hashing methods (such as `crypto.pbkdf2Sync` or `bcrypt.hashSync`) directly inside a request thread forces the event loop to stop processing everything else for the duration of the calculation. This blocks all other incoming requests from being read or responded to.
A circuit breaker pattern tracks call failures using three operational states: **Closed** (normal traffic), **Open** (failing fast without hitting the degraded dependency), and **Half-Open** (allowing a slow trickle of testing requests to pass through). You implement this pattern using libraries like `opossum` wrapped around outward-facing request closures.
The native `JSON.stringify` operation executes completely synchronously. If an API attempts to serialize a massive 50MB nested relational data object, the main loop locks up entirely while traversing the data structure. You optimize this by using schema-based serialization compilers like `fast-json-stringify`.
To mitigate cache stampede, you use an Asynchronous Request Coalescing layer (often called a Single-Flight pattern). When a cache miss happens, instead of letting all 500 concurrent requests query the database at once, you intercept them and ensure only the first request queries the DB while the other 499 hook into its returned Promise.
The default `highWaterMark` for readable streams is 16KB (and 64KB for writable streams). When streaming massive enterprise files, small buffer thresholds generate thousands of tiny chunk operations, causing high CPU overhead. Bumping this property to larger boundaries (e.g., 1MB or 4MB) optimizes the underlying system reads.
Standard string comparison operators (`===`) break evaluation early the exact millisecond a character mismatch is detected. An attacker can map out the character length of a secret key by measuring tiny nanosecond variations in response times. To solve this, you use fixed-time comparison functions.
Since WebSockets keep an open TCP connection to a specific server instance, Server 1 cannot natively communicate with a client connected to Server 2. You solve this horizontal scale limitation by putting an asynchronous message distribution layer (like Redis Pub/Sub) behind your WebSocket nodes.
You use Native C++ Addons when you need to perform heavy CPU-bound computing (like machine learning models, custom compression algorithms, or image manipulation) that exceeds the performance of the V8 JavaScript interpreter. Node-API provides binary stability across distinct Node versions.
V8 tracks standard JavaScript objects inside its heap layout, which requires regular garbage collection (GC) sweeps. If you store millions of objects in an in-memory cache, the GC pauses will grow longer and degrade performance. `TypedArrays` (like `Int32Array` or `Float64Array`) allocate raw un-managed binary buffers, bypassing V8 GC sweeps entirely.
Replication lag happens when data written to a primary database has not yet synced to a read replica. If a Node service writes a record and then immediately tries to read it from a replica, it will encounter a 404 Not Found anomaly. You solve this by implementing data-routing logic.
Express evaluates routes linearly in the order they are defined. If an application grows to have thousands of routes, matching a request at the bottom of the list requires checking every previous route structure, which incurs an $O(N)$ lookup penalty. You solve this by implementing Radix-Trie based routers like `find-my-way` or switching to Fastify.
Dynamic `import()` expressions allow you to load modules asynchronously at runtime based on variables. This enables you to load isolated tenant code blocks on demand, rather than pre-loading every custom module into memory at server startup.
Slowloris attacks consume server connection limits by opening sockets and sending tiny fragments of HTTP header data very slowly, keeping the connections open as long as possible. You defend against this by tuning the underlying server timeout limits, such as `requestTimeout`, `headersTimeout`, and `keepAliveTimeout` on the HTTP server instance.
When the V8 heap grows large, major garbage collection sweeps can cause “Stop-The-World” pauses that freeze execution for several seconds. You can mitigate this by tuning runtime options via your command configuration, using flags like `–max-old-space-size`, `–optimize-for-size`, and adjusting the incremental marking steps.
Loading millions of database rows directly into a standard array will quickly exhaust your server’s RAM and cause an out-of-memory crash. Instead, you should fetch the data using database streaming cursors and pipe those chunks directly into the HTTP response object.
To guarantee idempotency, every incoming transaction must pass a unique `Idempotency-Key`. Before executing any business logic, Node checks for the existence of this key in Redis using an atomic operation like `SETNX`. If the key already exists, the system bypasses execution and returns the original cached response.
You can identify memory trends programmatically by leveraging the native `v8` module’s heap profiling tools. By taking regular samples of heap allocations over time, you can flag specific object structures that are growing continuously without being cleaned up by the garbage collector.
Command Query Responsibility Segregation (CQRS) separates data modification operations (Commands) from data read operations (Queries). In Node, you can optimize this by using separate database models or entirely separate services for handling writes versus reads.
Because distinct operating system processes cannot share standard in-memory locks, you must implement mutual exclusion at the OS kernel level. This can be achieved by using file descriptor locks via system calls like `flock` or using the `fs-ext` library’s lock functionality.
Mutual TLS requires both the client and the server to validate each other’s X.509 certificates before establishing a connection. In Node, you configure this inside the `https` or `tls` module options by setting `requestCert: true` and `rejectUnauthorized: true`, along with your trusted Certificate Authority (CA) files.
When streaming data to a client over a slow network connection, the server’s writable stream buffer will fill up, causing backpressure. You handle this by listening for the `.write()` method’s `false` return value, pausing the upstream data source, and resuming it only when the socket fires the `’drain’` event.
A reentrancy issue occurs when an asynchronous function is called again before its first invocation has completely finished processing, which can lead to race conditions or unexpected state modifications if the function relies on shared variables.
Serverless functions often suffer from cold start delays while the runtime initializes and compiles code. You can optimize this by utilizing V8 code caching, which stores the compiled machine code of your modules so subsequent invocations can skip the compilation step entirely.
Using the native `vm` module to run untrusted code is unsafe because a malicious script can break out of the context and access the main process. For true isolation, you must execute untrusted code using advanced sandboxing libraries like `isolated-vm`, which runs code within isolated V8 Isolate instances.
Synchronizing data across regions requires an architecture that can handle network latency and potential network partitions. You can coordinate this by using globally distributed databases like Amazon Aurora Global Database or setting up multi-region message replication using systems like Apache Kafka.
Operational errors are predictable runtime failures (such as a database timeout or a invalid user input) that must be caught and handled gracefully. Programmer errors are bugs in the code (such as a `TypeError` or an undefined reference) that put the application into an unstable, unpredictable state.
Forcing garbage collection manually is generally discouraged because it interrupts execution flow. However, in heavy batch processing scripts that load and discard millions of data rows, you can run Node with the `–expose-gc` flag and call the global `gc()` function after processing each batch to free up memory immediately.
Distributed tracing requires passing tracing headers (like `traceparent`) across network requests. OpenTelemetry uses Context Propagation to inject these trace IDs into outgoing HTTP request headers or message broker properties, allowing downstream services to continue the same trace context.
A closure leak happens when a long-lived function or object retains a reference to a parent scope variable that is no longer needed. If that variable holds a large object (like a request context), that memory cannot be cleaned up by the garbage collector as long as the closure remains active.
Manually creating worker threads for individual tasks creates significant performance overhead. Instead, you should use a dedicated thread pool manager like `piscina`. It maintains a stable pool of reusable workers, queueing up incoming tasks and distributing them efficiently across available CPU cores.
HTTP/1.1 limits browsers or client agents to a small number of concurrent TCP connections (typically 6) per domain, meaning requests can get queued behind slow operations. HTTP/2 uses multiplexing to send multiple requests and responses concurrently over a single TCP connection, reducing connection overhead.
A Two-Phase Commit ensures atomic updates across multiple distinct database instances. It operates in two steps: 1) The coordinator service asks all participating databases to **Prepare** and lock the necessary records, and 2) If all databases report success, the coordinator issues a global **Commit** command; otherwise, it aborts the operation.
A sliding window rate limiter tracks timestamps for every request inside a Redis sorted set (`ZSET`). For each incoming request, the application runs a Redis pipeline that removes timestamps older than the current window, counts the remaining elements in the set, and adds the new timestamp if the limit hasn’t been breached.
Standard Redis commands like `GET` and `SET` run sequentially, but if multiple distinct Node processes execute them concurrently, you can still experience race conditions. You can solve this by writing your evaluation logic inside a Lua script. Redis executes Lua scripts atomically, ensuring no other commands can run mid-execution.
Monorepos use package manager workspaces (like npm, yarn, or pnpm workspaces) to manage multiple independent applications alongside shared utility libraries in a single repository. This allows projects to reference local packages directly without publishing them to an external registry.
Instead of restarting servers to pick up configuration changes, your Node.js application can listen for live configuration updates using a centralized system or a message broker channel like Redis Pub/Sub.
Standard V8 heap snapshots only show memory managed by the JavaScript engine; they will not show leaks that occur in raw C++ code. To track down unmanaged memory leaks inside native addons, you must profile the entire Node process using system-level tools like Valgrind, leaks, or TCMalloc.
Using standard `.pipe()` chaining does not forward errors downstream. If an error occurs in the first stream of a chain, it will go unhandled and crash the application. To handle errors safely across a multi-stage pipeline, you should use the native `stream.pipeline()` function, which catches errors at any stage and handles cleanup automatically.
Node.js Interview Questions: The Core Developer Round
50 Intermediate Practical Node.js Questions asked in Top MNCs focusing on Architecture, Performance, and Microservices.
Intermediate Level (Q1 – Q50)
`libuv` is a multi-platform C library that provides support for asynchronous I/O based on event loops. It abstracts non-blocking I/O operations (file system, networking) to provide a consistent interface across different operating systems, and it manages the internal thread pool.
The Event Loop runs in a specific order of phases: 1) Timers (`setTimeout`, `setInterval`), 2) Pending Callbacks (I/O callbacks deferred to the next iteration), 3) Idle/Prepare (internal use), 4) Poll (retrieve new I/O events), 5) Check (`setImmediate`), and 6) Close Callbacks (e.g., `socket.on(‘close’)`).
By default, `libuv` creates a thread pool of 4 threads to handle heavy tasks like DNS lookups, crypto operations, and file I/O. You can alter it by setting `process.env.UV_THREADPOOL_SIZE = ‘x’` (up to 1024) before your app boots.
Backpressure occurs when data is being read from a source faster than it can be written to a destination. The writable stream’s internal buffer fills up, and it must signal the readable stream to pause until the buffer drains to prevent memory exhaustion.
`.pipe()` automatically manages backpressure, but it does not automatically destroy connected streams if an error occurs in the middle of the chain, leading to memory leaks. `pipeline()` (from the `stream` module) safely handles errors and properly cleans up all streams involved.
A graceful shutdown involves listening to OS signals (`SIGINT`, `SIGTERM`), stopping the server from accepting new connections (`server.close()`), completing existing active requests, closing database connections, and finally calling `process.exit(0)`.
The `cluster` module forks the entire Node.js process. Each fork has its own V8 instance, memory, and Event Loop, but shares server ports (great for horizontal scaling). `worker_threads` share the same process memory (via ArrayBuffers) and are designed to offload CPU-heavy mathematical tasks within a single app instance.
You start the Node process with the `–inspect` flag and connect Chrome DevTools. You take a Heap Snapshot, run a load test against your API, and take another snapshot. By comparing the two (Heap Allocation Timeline), you can identify objects (like closures or arrays) that are not being garbage collected.
Callbacks registered with `process.nextTick()` are executed immediately after the current operation, before the Event Loop continues. If you recursively call `nextTick()`, you can cause “I/O Starvation,” where the Event Loop is locked and never reaches the Poll phase to handle incoming requests.
If you repeatedly attach listeners to an EventEmitter (e.g., inside a middleware or request handler) without removing them via `emitter.removeListener()`, the emitter retains a reference to the callback closures, preventing them from being garbage collected.
MaxListenersExceededWarning: Possible EventEmitter memory leak detected.Session cookies are stateful; the server stores a Session ID in memory/Redis and sends a cookie to the client. The server must check the DB on every request. JWT is stateless; the payload (e.g., user role) is cryptographically signed and stored on the client. The server mathematically verifies the signature without querying the database.
Storing JWTs in LocalStorage makes them vulnerable to Cross-Site Scripting (XSS). If an attacker injects malicious JavaScript into your site, that script can easily read `localStorage.getItem(‘token’)` and steal the user’s identity.
Cross-Site Request Forgery (CSRF) is when a malicious site tricks a user’s browser into making an authenticated request to your site (using their active cookies). You protect against it by implementing Anti-CSRF tokens (using packages like `csurf`) or setting the `SameSite` attribute on your cookies.
Rate limiting restricts the number of requests a client (usually identified by IP address) can make in a given timeframe. It is typically implemented using middleware like `express-rate-limit` backed by an in-memory store or Redis for distributed systems.
Redis is an in-memory key-value data store. Because Node is single-threaded, querying a traditional DB (like Postgres) for frequently accessed, unchanging data is slow. Redis acts as a caching layer, returning data in sub-milliseconds.
Opening a new database connection for every HTTP request is incredibly resource-intensive (TCP handshakes, authentication). A connection pool creates a set number of DB connections on startup and reuses them across requests.
You listen to `process.on(‘uncaughtException’)` and `process.on(‘unhandledRejection’)`. However, it is dangerous to keep the server running after an uncaught exception due to unpredictable state. You should log the error to a service (like Sentry), and then synchronously shut down the process (`process.exit(1)`), letting a process manager (PM2/Kubernetes) restart it cleanly.
The built-in `crypto` module provides cryptographic functionality that includes a set of wrappers for OpenSSL’s hash, HMAC, cipher, decipher, sign, and verify functions.
In Express, an error-handling middleware is distinct because it takes exactly four arguments: `(err, req, res, next)`. If you pass an argument to `next(err)` in any normal route, Express will bypass all standard middleware and jump straight to the nearest error handler.
Buffers allocate raw memory *outside* the V8 JavaScript engine heap, meaning they aren’t subject to V8’s memory limits (usually ~1.5GB). `Buffer.alloc()` creates a zero-filled, safe buffer. `Buffer.allocUnsafe()` skips zero-filling, which is faster but may contain old, sensitive data from RAM.
A reverse proxy (like NGINX or HAProxy) sits in front of your Node server. Node is excellent at running application logic but mediocre at handling raw network tasks. The proxy handles SSL termination, gzip compression, serving static assets, and load balancing, freeing Node to focus strictly on API logic.
PM2 is a production process manager for Node.js. It ensures your application stays online 24/7 by automatically restarting it if it crashes. It also provides built-in load balancing (Cluster mode), log management, and zero-downtime reloads.
Microservices often use an Event-Driven Architecture via Message Brokers (like RabbitMQ or Apache Kafka). Instead of Service A making a synchronous HTTP request to Service B, Service A publishes an event to a queue, and Service B consumes it asynchronously.
gRPC is a high-performance RPC framework developed by Google. Unlike REST, which uses JSON over HTTP/1.1, gRPC uses Protocol Buffers (binary data) over HTTP/2. It is strictly typed and vastly faster, making it ideal for internal microservice-to-microservice communication.
An API Gateway is a server that acts as a single entry point into a system of multiple backend microservices. It handles cross-cutting concerns like authentication, routing, rate limiting, and analytics.
Use environment variables. You rely on the `NODE_ENV` variable to dictate the environment. Libraries like `dotenv` load local `.env` files, but in production, variables should be injected by the host (AWS Parameter Store, Kubernetes Secrets, or Docker Compose).
Dependency Injection (DI) is a design pattern where an object receives its dependencies from the outside rather than creating them internally (e.g., using `require`). This decouples code and makes unit testing incredibly easy.
Test-Driven Development (TDD) is writing tests before writing the actual code. In Node, standard tools include Jest or Mocha/Chai for unit and integration testing, and Supertest for testing Express HTTP endpoints.
Jest provides `jest.mock()` to replace the actual implementation of a module with a dummy version. This isolates the function you are testing by preventing side effects like actual HTTP calls or database writes.
By default, `express-session` uses a MemoryStore, which stores session data in the Node process RAM. If the server restarts, all users are logged out. A Persistent Store (like `connect-redis` or `connect-pg-simple`) saves session data externally.
An index is a data structure (usually a B-Tree) that improves the speed of data retrieval operations on a database table. If your Express API queries a table by `email`, applying an index to the `email` column prevents a slow “full table scan”.
A transaction is a sequence of DB operations that must all succeed, or all fail completely (ACID properties). You start a transaction, run queries, and then either `COMMIT` or `ROLLBACK` on error.
Never trust client data. You should validate payloads at the route level before they reach your controllers. Popular libraries include Joi, Zod, or express-validator to enforce data types, lengths, and strict schemas.
Before sending a complex HTTP request (like PUT/DELETE, or containing custom headers), the browser automatically sends an `OPTIONS` request called a preflight. It asks the Node server, “Are you okay with me sending this request?”
SSE is a standard allowing a client to receive automatic updates from a server via an HTTP connection. Unlike WebSockets (which are bi-directional), SSE is uni-directional (Server to Client) and operates over standard HTTP.
HTTP is stateless and strictly Request-Response (client must ask, server answers). WebSockets create a persistent, full-duplex TCP connection. Both the client and server can push messages to each other at any time.
The N+1 problem occurs when you query a list of items (1 query), and then iterate over that list, executing an additional query for each item (N queries). This crushes performance. It is solved using tools like DataLoader (batching) or DB joins.
Bull is a Redis-based queue for Node.js used to handle background jobs. You use it to offload tasks that take a long time and don’t require an immediate HTTP response to the user.
Health monitoring involves exposing a `/health` endpoint, collecting metrics (CPU, Memory, Request latency) using APM tools like Datadog, New Relic, or Prometheus, and setting up alerts for anomaly detection.
When a frontend app needs data from a 3rd party API that does not support CORS, the browser will block the request. You can create a proxy route on your Node backend to fetch the data on behalf of the client (servers don’t enforce CORS on outbound requests) and return it to the frontend.
When you `require()` a module in Node, it is evaluated once, and the result is cached in `require.cache`. Subsequent calls to `require` for the same file will return the exact same cached object reference without re-executing the code.
Content negotiation is an HTTP mechanism where the client tells the server what format of data it expects (using the `Accept` header), and the server (e.g., Express) formats the response accordingly (JSON, XML, HTML).
File uploads are sent using `multipart/form-data`. Express cannot parse this out of the box. You must use middleware like Multer or Busboy to intercept the binary stream, parse the file, and save it to disk or a cloud bucket.
Both methods are highly synchronous and CPU-intensive. Parsing massive JSON payloads (megabytes in size) will block the Event Loop entirely while V8 evaluates the string.
An API endpoint is idempotent if making multiple identical requests has the same effect as making a single request. `GET`, `PUT`, and `DELETE` should be idempotent. `POST` is typically not (it creates a new resource each time).
You use a Node Version Manager like NVM (or Volta) and include a `.nvmrc` file at the root of your project containing the specific Node version (e.g., `v18.17.0`). Additionally, you lock engines in `package.json`.
An ETag (Entity Tag) is an HTTP response header that provides a unique hash representing the requested resource. Express generates this automatically for responses.
The `os` module provides operating system-related utility methods. It allows a Node app to query the underlying server for metrics like total memory, free memory, CPU architecture, and network interfaces.
Prisma is a Next-Generation ORM that replaces traditional class-based models with a custom schema file. It auto-generates a fully type-safe query builder customized exactly for your database, offering vastly superior TypeScript integration and auto-completion compared to older ORMs.
The Thundering Herd problem occurs when a highly trafficked cache key expires, and suddenly hundreds of concurrent requests all hit the primary database simultaneously to rebuild the cache, causing the DB to crash.
Node.js Interview Questions: The Screening Round
50 Essential Beginner Node.js Interview Questions & Answers frequently asked by top MNCs.
Beginner Level (Q1 – Q50)
Node.js is an open-source, cross-platform JavaScript runtime environment built on Chrome’s V8 JavaScript engine. It allows developers to execute JavaScript on the server-side, outside of a web browser.
Browser JS interacts with the DOM (Document Object Model), handles UI events, and has no direct access to the OS. Node.js lacks the DOM but has direct access to the operating system, file system, and network interfaces via built-in modules.
V8 is Google’s open-source high-performance JavaScript and WebAssembly engine, written in C++. It compiles JavaScript directly into native machine code rather than interpreting it in real-time.
Non-blocking I/O means that the Node.js process does not halt (block) when performing Input/Output operations like reading a file or querying a database. It delegates the task and continues executing subsequent code.
Node.js operates on a single main thread using an event loop. However, behind the scenes, it utilizes a C++ library called `libuv` which maintains a thread pool to handle heavy asynchronous I/O tasks.
The Event Loop is the mechanism that allows Node.js to perform non-blocking I/O operations despite being single-threaded. It constantly monitors the call stack and callback queue, pushing resolved asynchronous callbacks onto the stack when it’s empty.
NPM stands for Node Package Manager. It consists of a massive online registry of open-source JavaScript tools/libraries, and a command-line utility used to install and manage these packages in your project.
It is the manifest file of a Node.js project. It holds metadata like the project’s name, version, author, custom scripts, and most importantly, the lists of dependencies (packages) required to run the application.
`dependencies` are packages required for the application to run in production (e.g., Express, Mongoose). `devDependencies` are only needed for local development and testing (e.g., Jest, Nodemon, ESLint).
A callback is a function passed as an argument into another function. In Node.js, callbacks are heavily used to execute code only after an asynchronous operation has completed.
Callback Hell (or the Pyramid of Doom) occurs when multiple asynchronous operations depend on each other, leading to deeply nested, unreadable, and hard-to-maintain callback functions.
A Promise represents the eventual completion (or failure) of an asynchronous operation. Instead of nesting callbacks, Promises allow you to chain `.then()` and `.catch()` blocks, flattening the code structure.
Introduced in ES8, `async/await` is syntactic sugar over Promises. It allows developers to write asynchronous code that visually looks and behaves like synchronous, blocking code, while remaining non-blocking under the hood.
The `fs` (File System) is a core Node.js module used to interact with the file system. It provides methods to read, write, update, delete, and rename files.
`readFile` is asynchronous and non-blocking, requiring a callback. `readFileSync` is synchronous and blocking; it completely halts the Node process until the file is fully read.
The `path` module provides utilities for working with file and directory paths. It is essential because different operating systems use different path separators (Windows uses `\` while Linux/Mac uses `/`).
The `http` module is a core module that allows Node.js to transfer data over the Hyper Text Transfer Protocol (HTTP). It can be used to create an HTTP server that listens to server ports and gives a response back to the client.
The `EventEmitter` class (from the `events` module) is used to trigger and listen to custom events. You use `.on()` to listen for an event and `.emit()` to trigger it.
Streams are objects that let you read data from a source or write data to a destination in continuous, small chunks rather than loading the entire payload into RAM at once.
1) Readable (read data), 2) Writable (write data), 3) Duplex (both read and write), 4) Transform (read, modify, and write).
A Buffer is a temporary storage spot for a chunk of raw binary data. It is primarily used by streams to hold data while it is being transferred from one place to another.
The `process` object provides information about, and control over, the current Node.js process. It is a global object, meaning it is available everywhere without importing.
Express.js is a minimal and flexible Node.js web application framework. It provides a robust set of features for building web and mobile APIs quickly, handling routing, requests, and responses efficiently.
Middleware functions are functions that have access to the request object (`req`), the response object (`res`), and the `next` middleware function in the application’s request-response cycle. They can execute code, make changes to req/res, or end the cycle.
Cross-Origin Resource Sharing (CORS) is a security feature implemented by browsers. By default, a browser won’t let a frontend (e.g., your-react-app.com) make API calls to a backend on a different domain (e.g., your-node-api.com) unless the backend explicitly allows it via CORS headers.
`req.params` contains route parameters (part of the URL path), while `req.query` contains URL query string parameters (everything after the `?` in the URL).
You use the built-in middleware `express.json()`. Without this, if a client sends a JSON payload (like a signup form) via a POST request, `req.body` will be undefined.
`dotenv` is a zero-dependency module that loads environment variables from a `.env` file into `process.env`.
`nodemon` is a development utility that monitors your project for any file changes and automatically restarts your Node.js server.
Express uses methods corresponding to HTTP verbs (e.g., `app.get()`, `app.post()`). For organizing complex apps, Express provides `express.Router()` to create modular, mountable route handlers.
An uncaught exception is a JavaScript error that is thrown but not caught in a `try/catch` block. By default, this immediately crashes the Node.js process.
`Promise.all()` takes an array of promises and runs them concurrently, returning a single Promise that resolves when all of the promises have resolved (or rejects if any one of them fails).
CommonJS uses `require()` and `module.exports` and is loaded synchronously. ES Modules (ESM) use `import` and `export`, are standard across browsers and modern JS, and are loaded asynchronously.
Because Node is single-threaded, running a heavy, blocking synchronous task (like a massive mathematical `while` loop) prevents the Event Loop from processing any new incoming network requests.
`setTimeout(fn, 0)` schedules a script to run after a minimum threshold in ms. `setImmediate(fn)` schedules a script to run immediately after the current phase of the Event Loop (I/O polling) is complete.
`process.nextTick()` schedules a callback function to be executed exactly after the current operation completes, but before the Event Loop moves on to the next phase.
Node interacts with databases using specific driver packages installed via npm. Common choices are `mongoose` for MongoDB, `pg` for PostgreSQL, or ORMs like `Prisma` or `Sequelize`.
JWT is a standard for securely transmitting information between a client and a server as a JSON object. It is most commonly used for stateless user authentication.
`bcrypt` is a library used to securely hash passwords. Hashing is a one-way mathematical function. You cannot “decrypt” a hash back to the original password.
Helmet is a middleware package for Express that automatically secures your application by setting various HTTP response headers.
SemVer is a versioning system used in `package.json` denoted by three numbers: MAJOR.MINOR.PATCH (e.g., `1.4.2`).
While `package.json` specifies acceptable version ranges (e.g., `^1.4.0`), `package-lock.json` records the exact, precise version of every dependency and sub-dependency that was actually installed.
The `cluster` module allows you to easily create child processes (workers) that run simultaneously and share the same server port. This takes advantage of multi-core systems.
Introduced to handle CPU-intensive tasks, the `worker_threads` module allows Node.js to execute JavaScript in parallel using isolated V8 environments, without blocking the main event loop.
Never concatenate raw user input directly into database query strings. Always use parameterized queries (prepared statements) or use an ORM/ODM (like Mongoose or Prisma) which sanitizes inputs automatically.
While `console.log()` is common, professional debugging is done by starting Node with the `–inspect` flag, or by using the built-in debugging tools in an IDE like VS Code to set breakpoints.
Application Programming Interface. In the context of Node, it’s a set of URL endpoints that allow frontend applications (like React, or a mobile app) to communicate with your backend logic and database.
Representational State Transfer (REST) is a software architectural style. A RESTful API relies on standard HTTP methods (GET, POST, PUT, DELETE) linked to specific URLs (resources) in a stateless manner.
By using the `-g` flag (`npm install -g package-name`), the package is installed on your operating system globally, rather than in the local `node_modules` of a specific project. This is usually reserved for CLI tools.
A memory leak occurs when a Node app allocates memory (RAM) but fails to release it back to the OS when it’s no longer needed (garbage collection failure). Over time, RAM fills up and the server crashes.