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)

Q01
What is the specific role of `libuv` in Node.js architecture?
Intermediate

`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.

Real-World Example: When you call `fs.readFile()`, V8 delegates this to `libuv`. `libuv` talks to the OS using epoll (Linux) or IOCP (Windows) to read the file in the background, waking up the main JavaScript thread only when the data is ready.
Q02
Explain the phases of the Node.js Event Loop.
Intermediate

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’)`).

Problem Scenario: If you put a heavy synchronous script inside a `setTimeout`, it blocks the Timers phase. Consequently, the Poll phase is delayed, meaning your server temporarily stops accepting incoming network requests.
Q03
How and why would you alter the `UV_THREADPOOL_SIZE`?
Intermediate

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.

Real-World Example: If your Node server handles heavy `bcrypt` hashing on user login, and 5 users log in simultaneously, the 5th user must wait because the default 4 threads are occupied. Bumping the pool size to 8 (if your CPU has the cores) drastically reduces the 5th user’s latency.
Q04
What is backpressure in Node.js streams?
Intermediate

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.

Problem Scenario: You are downloading a 5GB file (fast readable stream) and writing it to a slow hard drive (slow writable stream). Without handling backpressure, Node will cache the excess data in RAM, eventually crashing the server with an Out of Memory (OOM) error.
Q05
How does `.pipe()` differ from the new `pipeline()` method?
Intermediate

`.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.

Real-World Example: When piping a user upload to AWS S3, always use `pipeline(req, s3Stream, (err) => {…})`. If the user disconnects their WiFi mid-upload, `pipeline` destroys the S3 stream immediately, whereas `.pipe()` would leave it hanging.
Q06
How do you gracefully shutdown a Node.js server?
Intermediate

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)`.

Real-World Example: In Kubernetes, pods are frequently destroyed during scaling. If you don’t catch `SIGTERM` and shutdown gracefully, users in the middle of a checkout process will receive aggressive connection reset errors instead of completing their transaction.
Q07
What is the difference between `cluster` and `worker_threads`?
Intermediate

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.

Real-World Example: Use `cluster` (or PM2) to scale your Express API across an 8-core CPU to handle more HTTP traffic. Use `worker_threads` inside a specific route to generate a complex PDF report without blocking the rest of the API.
Q08
How do you debug memory leaks in Node.js?
Intermediate

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.

Problem Scenario: Your server restarts every 3 days. By taking heap snapshots, you discover a Winston logger configuration is inadvertently storing every incoming HTTP request object in a global array instead of writing them to a file.
Q09
Why is `process.nextTick()` dangerous if misused?
Intermediate

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.

Real-World Example: Using a recursive `process.nextTick()` to process a massive array of 1,000,000 items will completely freeze the server for all other users. For heavy iterations, use `setImmediate()` to allow I/O to be processed in between chunks.
Q10
Explain the concept of “Event Emitter Memory Leaks.”
Intermediate

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.

Problem Scenario: Doing `process.on(‘message’, handler)` inside a user request route means every time a user hits that route, a new listener is added. Node will eventually warn you: MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
Q11
How does JWT differ from Session Cookies?
Intermediate

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.

Real-World Example: For microservices, JWTs are vastly superior. An Auth service generates the JWT, and a completely separate Billing service can verify the user’s identity independently without needing to connect to the Auth database.
Q12
What are the security vulnerabilities of storing JWTs in LocalStorage?
Intermediate

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.

Problem Scenario: The most secure way to store tokens in a web app is to have the Node backend send the JWT as an `HttpOnly`, `Secure` cookie. JavaScript cannot read `HttpOnly` cookies, completely mitigating token theft via XSS.
Q13
What is CSRF and how do you protect a Node API against it?
Intermediate

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.

Real-World Example: A user logs into their bank. In another tab, they click a malicious link that submits a hidden POST request to `bank.com/transfer`. Because `SameSite=Strict` is configured in your Express cookie settings, the browser blocks the cookie from being sent via the malicious site.
Q14
How do you implement Rate Limiting in Express?
Intermediate

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.

Real-World Example: To prevent brute-force credential stuffing on your `/login` route, you apply a strict rate limit: maximum 5 requests per 15 minutes per IP. Any further requests automatically return an HTTP 429 (Too Many Requests).
Q15
Why use Redis with a Node.js application?
Intermediate

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.

Real-World Example: An e-commerce API fetching a list of product categories. Since categories rarely change, you query Postgres once, store the JSON string in Redis, and serve all subsequent requests directly from Redis, dropping response times from 100ms to 2ms.
Q16
Explain Connection Pooling.
Intermediate

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.

Problem Scenario: Using `pg` (PostgreSQL client) without a pool in a high-traffic app will rapidly hit the database’s max connection limit, causing the API to crash. Using `new Pool({ max: 20 })` ensures requests wait in line for an available connection rather than crashing the DB.
Q17
How do you handle uncaught exceptions and unhandled promise rejections safely?
Intermediate

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.

Problem Scenario: Ignoring unhandled rejections used to just print a warning, but in modern Node.js versions (v15+), an unhandled Promise rejection automatically crashes the entire application by default.
Q18
What is the purpose of the `crypto` module?
Intermediate

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.

Real-World Example: When integrating Stripe webhooks, you receive an HTTP request claiming to be from Stripe. You use `crypto.createHmac()` with your Stripe Secret to hash the payload. If your hash matches the header signature, you process the payment; otherwise, it’s an attacker.
Q19
Explain the concept of Middleware Error Handling in Express.
Intermediate

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.

Real-World Example: You create a central `errorHandler.js` at the bottom of your server file. Instead of writing `res.status(500).send()` in 50 different catch blocks, you simply write `catch (err) { next(err); }`. The central handler standardizes the error response and logs it.
Q20
How does the `Buffer` class allocate memory?
Intermediate

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.

Problem Scenario: If you use `Buffer.allocUnsafe(100)` and send it straight to an HTTP client without filling it first, you might accidentally leak encryption keys or passwords that were previously occupying that sector of RAM.
Q21
What is a Reverse Proxy and why put Node.js behind one?
Intermediate

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.

Real-World Example: Exposing a Node API directly to port 80/443 on the public internet is a security risk. NGINX absorbs slow-loris attacks and invalid HTTP requests before they even reach your Express app.
Q22
What is the role of PM2?
Intermediate

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.

Real-World Example: Deploying an update. Without PM2, you stop the server, pull code, and start it—causing 10 seconds of downtime. With `pm2 reload`, PM2 spawns new workers with the new code, switches traffic to them, and kills the old ones, resulting in zero dropped user requests.
Q23
How would you architect a messaging system in Microservices using Node?
Intermediate

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.

Real-World Example: An Order Service successfully processes payment. It drops an `OrderCreated` message into RabbitMQ. The Email Service picks it up and sends a receipt, while the Inventory Service picks it up to reduce stock. If the Email service is down, the message stays in the queue until it comes back online.
Q24
What is gRPC and how does it compare to REST in Node.js?
Intermediate

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.

Problem Scenario: Two internal Node microservices communicating via REST parsing massive JSON payloads will experience high CPU usage and latency. Switching to gRPC reduces payload size by ~60% and increases parsing speed dramatically.
Q25
What is an API Gateway?
Intermediate

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.

Real-World Example: A mobile app doesn’t need to know the IPs of your Auth Service, User Service, and Order Service. It makes all requests to `api.gateway.com`, and the Gateway (often built with Node/Express or Kong) verifies the token and routes the request to the correct internal Docker container.
Q26
How do you manage configuration for different environments (Dev, Staging, Prod)?
Intermediate

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).

Problem Scenario: Hardcoding DB URLs based on `if (env === ‘prod’)` is an anti-pattern. Your code should strictly use `process.env.DATABASE_URL`. The environment host decides what that URL is, keeping your codebase environment-agnostic.
Q27
Explain the concept of Dependency Injection in Node.js.
Intermediate

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.

Real-World Example: Instead of importing a DB connection inside a User Controller, you pass the DB connection into the controller’s constructor. During testing, you can pass in a “Mock Database” object to test the controller logic without hitting a real database.
Q28
What is TDD and what tools do you use in Node.js?
Intermediate

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.

Problem Scenario: You write a `user.spec.js` file using Supertest to assert that `POST /login` returns a 200 and a token. You wire this into a GitHub Actions CI pipeline. If a junior developer pushes code that breaks login, the CI pipeline fails, preventing the deployment.
Q29
How do you mock modules in Jest?
Intermediate

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.

Real-World Example: If testing a function that sends an email via SendGrid, you do `jest.mock(‘sendgrid’)`. When the test runs, it verifies that the email function *would have* been called with the correct parameters, without actually sending a spam email to a real user.
Q30
What is a Memory Store vs Persistent Store for Sessions?
Intermediate

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.

Problem Scenario: If you scale your Node app to 3 load-balanced instances, a user logging in on Instance 1 will be unauthorized if their next request is routed to Instance 2. Storing sessions centrally in Redis solves this.
Q31
Explain the concept of Database Indexing and how it relates to API speed.
Intermediate

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”.

Real-World Example: Your `/users/search` endpoint takes 5 seconds to query a table of 1 million users. After running `CREATE INDEX on users(email)`, the DB jumps straight to the record, and the API response time drops to 50ms.
Q32
What is a Database Transaction and how do you handle it in Node?
Intermediate

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.

Real-World Example: A bank transfer. You deduct $100 from User A, and add $100 to User B. If the Node server crashes exactly between these two queries, $100 is lost. Wrapping them in a transaction ensures that if the second query fails, the first is automatically rolled back.
Q33
How do you validate incoming API payloads?
Intermediate

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.

Problem Scenario: An attacker sends a string instead of an integer for an `age` field, hoping to crash your DB query. A Zod schema checks the payload early and returns a clean 400 Bad Request to the client without touching your business logic.
Q34
What is CORS preflight?
Intermediate

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?”

Real-World Example: If your frontend sends a POST request with an `Authorization: Bearer ` header, the browser fires an OPTIONS request first. Your Express app (via the `cors` middleware) must respond with 204 OK and proper headers before the actual POST is allowed.
Q35
What is Server-Sent Events (SSE)?
Intermediate

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.

Real-World Example: Building a live stock ticker. Setting headers to `Content-Type: text/event-stream` and `Connection: keep-alive` in Express allows you to continuously `res.write()` price updates to the client without the client needing to refresh or poll.
Q36
How do WebSockets differ from HTTP?
Intermediate

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.

Problem Scenario: In a multiplayer browser game, making 10 HTTP requests per second for player movement causes massive overhead from HTTP headers. WebSockets (via `socket.io`) send tiny, instant binary frames, making real-time gaming possible.
Q37
What is the “N+1” query problem and how do you solve it in Node (GraphQL/ORMs)?
Intermediate

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.

Real-World Example: Fetching 50 blog posts, then looping through them to fetch the author for each = 51 database queries. Using `.populate(‘author’)` in Mongoose, or DataLoader in GraphQL, batches this into just 2 queries.
Q38
Why use a task queue like Bull/BullMQ?
Intermediate

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.

Real-World Example: A user clicks “Export Year History to CSV”. This takes 2 minutes. Instead of keeping the HTTP request open (which will timeout), you add a job to Bull, return a 202 Accepted, and have a background worker process the CSV and email it to the user.
Q39
How do you monitor the health of a Node.js application in production?
Intermediate

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.

Problem Scenario: Kubernetes relies on a “Liveness Probe”. By pinging `your-api.com/health` every 10 seconds, Kubernetes knows if your app is stuck in an infinite loop. If the endpoint stops responding 200 OK, Kubernetes automatically destroys the pod and starts a fresh one.
Q40
What is CORS proxying and when would you use it?
Intermediate

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.

Real-World Example: Your React app needs to fetch an RSS feed from a legacy news site. The browser blocks it. You make your React app call `GET /api/news` on your Node server, and Node uses `axios` to fetch the XML, convert it to JSON, and send it to React.
Q41
Explain how the `require` caching mechanism works.
Intermediate

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.

Problem Scenario: If you export an instantiated object (e.g., `module.exports = new Database()`), every file that requires it shares the *exact same* connection state (a Singleton). If you need distinct instances, you must export the class or a factory function instead.
Q42
What is Content Negotiation?
Intermediate

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).

Real-World Example: Express provides `req.accepts()`. If a web browser hits your route, you can return a rendered HTML view. If a mobile app hits the exact same route with `Accept: application/json`, you return a raw JSON object.
Q43
How do you handle file uploads in Express?
Intermediate

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.

Real-World Example: Using `multer({ dest: ‘uploads/’ })`, a user’s avatar image is intercepted, given a unique hash filename, and saved to the folder. Multer attaches the file metadata to `req.file` so your controller can save the path to the database.
Q44
What are the performance costs of JSON.parse / JSON.stringify?
Intermediate

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.

Problem Scenario: If you query 50,000 rows from Postgres and do `JSON.stringify(rows)`, the thread locks. For massive data transfers, you should use JSONStream (streaming the data chunk-by-chunk) rather than loading it all into memory.
Q45
Explain the concept of “Idempotency” in API design.
Intermediate

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).

Real-World Example: If a user clicks “Checkout” but their WiFi drops, their phone might retry the `POST /charge` request. If not built carefully, they get charged twice. Using an Idempotency-Key in headers ensures the Node backend recognizes the duplicate and ignores the second charge.
Q46
How do you manage Node.js versions in a team environment?
Intermediate

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`.

Problem Scenario: Developer A is on Node 20 (which has native `fetch`) and pushes code. Developer B is on Node 16 and pulls the code; their server immediately crashes. A `.nvmrc` file forces the whole team to use identical environments.
Q47
What are ETags and how do they optimize API performance?
Intermediate

An ETag (Entity Tag) is an HTTP response header that provides a unique hash representing the requested resource. Express generates this automatically for responses.

Real-World Example: The client requests a user profile. Express sends it with `ETag: “1234”`. On the next request, the client sends `If-None-Match: “1234”`. If the profile hasn’t changed, Express intercepts this, stops generating JSON, and simply returns a 304 Not Modified, saving massive bandwidth.
Q48
Explain the purpose of the `os` module.
Intermediate

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.

Real-World Example: Before spinning up heavy worker threads or forking clusters, you use `os.cpus().length` to dynamically determine exactly how many cores are available on the specific AWS EC2 instance running the code.
Q49
Why use Prisma over traditional Mongoose/Sequelize ORMs?
Intermediate

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.

Real-World Example: In Sequelize, `const user = await User.findOne()` might return `any` type, leading to hidden runtime errors. In Prisma, hovering over `user` instantly shows exact properties based on your DB schema, catching errors at compile time.
Q50
What is the “Thundering Herd” problem and how do caching strategies fix it?
Intermediate

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.

Problem Scenario: A popular news article’s Redis cache expires. 500 users hit the route. You fix this using “Cache Stampede Protection” (like Mutex locks or `redlock`), ensuring only the first request queries the database while the other 499 requests wait a few milliseconds for the new cache to be set.