Popular Searches
Popular Course Categories
Popular Courses

Node.js & Express Interview Questions 2026: REST, GraphQL, Auth & Performance

What Our Students Say
Advanced Node.js and Express Interview Questions Covering REST APIs GraphQL JWT Authentication and Performance Optimization 2026

Advanced Node.js and Express Interview Questions Covering REST APIs, GraphQL, JWT Authentication, and Performance Optimization — Complete 2026 Backend Developer Interview Guide

Why Node.js and Express Interview Questions Are Harder Than Ever in 2026

Backend development interviews have changed dramatically. In 2020, knowing how to set up an Express route and connect to MongoDB was enough to clear a Node.js interview at most companies. In 2026, that same knowledge barely gets you through the first screening round. Companies hiring Node.js and Express developers in 2026 expect candidates to demonstrate deep understanding of the event loop and asynchronous execution model, production-grade API design for both REST and GraphQL, JWT authentication with refresh token rotation and security hardening, performance optimization at the code level and infrastructure level, and the operational concerns of running Node.js in production under real-world load.

The reason for this raising of standards is straightforward: Node.js is no longer a startup technology. Node.js powers the backends of companies like Netflix, LinkedIn, Walmart, NASA, PayPal, and thousands of enterprises that handle millions of daily active users. When a company hires a Node.js developer in 2026, they are hiring someone to build and maintain systems at that scale. The interview questions reflect that expectation.

This guide covers the most important Node.js and Express interview questions that companies are asking in 2026 across five critical areas: Node.js core concepts and the event loop, Express.js middleware and routing, REST API design and best practices, GraphQL implementation, and JWT authentication with security considerations. Every answer is written at the depth that senior backend developer interviews at product companies require — not just what things are, but how they work internally, what their trade-offs are, and when to use one approach over another.

Whether you are a fresher targeting your first Node.js backend role or an experienced developer interviewing for a senior backend engineer or tech lead position, every question in this guide is something you will encounter in a real 2026 interview. This is the complete preparation resource you need.
Learn more about Node JS training here.

Table of Contents

  1. Node.js Core Concepts and Event Loop Interview Questions
  2. Express.js Middleware and Routing Interview Questions
  3. REST API Design and Best Practices Interview Questions
  4. GraphQL with Node.js Interview Questions
  5. JWT Authentication and Security Interview Questions
  6. Node.js Performance Optimization Interview Questions
  7. Frequently Asked Questions

Node.js Core Concepts and Event Loop Interview Questions

These questions form the foundation of every Node.js interview. Companies ask them at every experience level because they reveal whether a developer truly understands how Node.js works or is simply familiar with its APIs. A developer who understands the event loop can reason about performance problems, async bugs, and concurrency issues that developers without this understanding cannot diagnose.

Question 1. What is the Node.js event loop and how does it work?

The event loop is the mechanism that enables Node.js to perform non-blocking I/O operations despite JavaScript being single-threaded. It is the core of what makes Node.js architecturally different from threaded server environments like Java's thread-per-request model and the reason Node.js excels at handling large numbers of concurrent connections with minimal resource consumption.

Node.js runs JavaScript on a single main thread. When the application initiates an I/O operation — a database query, a file read, an HTTP request to an external service — Node.js offloads the actual I/O work to the operating system or to libuv's thread pool (for operations the OS cannot handle asynchronously, such as file system operations on some platforms). The main JavaScript thread does not wait for the operation to complete. It continues executing other code. When the I/O operation finishes, the operating system notifies libuv, which places the callback associated with that operation into the appropriate event queue. The event loop picks up callbacks from these queues and executes them on the main thread when it is free.

The event loop cycles through several phases repeatedly. The timers phase executes callbacks that were scheduled by setTimeout and setInterval whose delay threshold has been reached. The I/O callbacks phase executes callbacks for completed I/O operations that were not processed in the previous iteration. The poll phase retrieves new I/O events and executes their callbacks — this is where the event loop spends most of its waiting time between events. The check phase executes callbacks registered with setImmediate. The close callbacks phase executes callbacks for closed connections and resources.

Between each phase transition, Node.js processes the nextTick queue and the microtask queue. Process.nextTick callbacks run before microtasks (Promise callbacks), and both run before the event loop moves to the next phase. This prioritization means process.nextTick callbacks always execute before any I/O callbacks and before the next event loop iteration begins.

The interviewer follow-up on this question is always about what happens when you block the event loop. Since JavaScript runs on a single thread, any synchronous CPU-intensive operation — a large sort, heavy JSON parsing, a complex calculation — blocks the event loop for the duration of that operation. While the main thread is busy with synchronous work, no I/O callbacks, timer callbacks, or incoming request handlers can execute. This is why CPU-intensive work in Node.js must be offloaded to worker threads or child processes — running it on the main thread blocks all other operations and makes the server unresponsive.

Question 2. What is the difference between process.nextTick(), setImmediate(), and setTimeout(fn, 0)?

All three schedule a callback to execute asynchronously, but they execute at different points in the event loop with different priorities, and choosing the wrong one causes subtle timing bugs.

process.nextTick() adds its callback to the nextTick queue, which is processed after the current operation completes but before the event loop moves to its next phase. This means process.nextTick callbacks execute before any I/O callbacks, before any timer callbacks, and before any setImmediate callbacks, regardless of when they were registered. process.nextTick has the highest priority of all asynchronous scheduling mechanisms. Use it when you need a callback to run as soon as possible after the current synchronous operation, before any I/O or timer events are processed — commonly used in library code to ensure that events are emitted asynchronously even when data is synchronously available, to preserve callback ordering guarantees.

setImmediate() schedules a callback to execute in the check phase of the current or next event loop iteration, after I/O callbacks have been processed. setImmediate is designed to execute after the current poll phase's I/O callbacks complete, giving I/O callbacks priority over setImmediate callbacks within a given event loop iteration. Use setImmediate when you want to execute a callback after I/O events in the current iteration but do not need the maximum priority that process.nextTick provides.

setTimeout(fn, 0) schedules a callback to execute in the timers phase of the next event loop iteration, with a minimum delay of approximately 1 millisecond (the minimum timer resolution). It does not guarantee execution at exactly 0 milliseconds — the actual delay depends on system load and the precision of the timer implementation. In practice, setTimeout(fn, 0) may execute before or after setImmediate depending on the context — in the main module, their order is non-deterministic, but within an I/O callback, setImmediate always executes before setTimeout(fn, 0).

The tricky interview point is recursive process.nextTick calls. If you call process.nextTick recursively (a callback that calls process.nextTick again), the nextTick queue never empties, and the event loop never advances to I/O or timer phases — effectively starving all other operations. This is a known footgun in Node.js that senior interviewers specifically ask about.

Question 3. What are Node.js Worker Threads and when should you use them?

Worker Threads are Node.js's mechanism for running JavaScript in parallel on separate threads, enabling true multi-threaded execution for CPU-intensive operations without blocking the main event loop thread.

The Node.js worker_threads module provides the Worker class for creating new threads, MessageChannel and MessagePort for bidirectional communication between threads, and SharedArrayBuffer for sharing memory between threads without serialization. Each Worker runs its own V8 instance and its own event loop, providing genuine parallelism on multi-core systems. Communication between the main thread and workers uses message passing with postMessage and the message event — by default, data is copied (serialized and deserialized) between threads. SharedArrayBuffer allows sharing raw memory between threads for high-performance scenarios where copying would be too slow.

Use Worker Threads for CPU-intensive JavaScript operations that would block the main event loop: parsing large JSON documents (multi-megabyte JSON responses from external services), image processing and compression, video transcoding, cryptographic operations, complex mathematical calculations (financial modeling, scientific computing), compiling templates or code, and any operation where a JavaScript computation takes more than a few milliseconds.

Do not use Worker Threads for I/O-bound operations. Database queries, HTTP requests, and file reads are already handled asynchronously by libuv — the main thread is not blocked by I/O. Adding Worker Threads for I/O-bound work adds overhead (thread creation, message serialization) without providing any parallelism benefit because the I/O is already non-blocking.

The compute() utility pattern — offloading a single function to a Worker Thread, getting the result, and terminating the thread — is useful for occasional heavy operations. For sustained heavy computation, maintaining a Worker Thread pool (using the piscina package or a manual pool) amortizes the thread creation overhead and keeps threads available for immediate use without the startup cost of creating a new thread for each operation.

Question 4. What is the difference between CommonJS modules and ES Modules in Node.js 2026?

This question is asked in virtually every senior Node.js interview in 2026 because the module system ecosystem has been in a complex transition for several years and understanding the differences is essential for working on modern Node.js projects.

CommonJS (CJS) is the original Node.js module system using require() for importing and module.exports for exporting. CommonJS modules are loaded synchronously — when Node.js encounters a require() call, it loads the module completely before continuing execution. CommonJS modules are dynamically resolvable — you can call require() inside a function, inside a conditional, or with a dynamically computed path. CommonJS has been the Node.js standard for over a decade and the vast majority of npm packages use CommonJS.

ES Modules (ESM) use the import and export syntax standardized in ES6. ES Modules are statically analyzable — import statements must be at the top level of a file and cannot be conditional, which enables tree-shaking and other build-time optimizations. ES Modules support top-level await — you can use await at the module level without wrapping it in an async function. ES Modules are the standard for browsers and the direction of the JavaScript ecosystem. Node.js has supported ES Modules since version 12 with improvements through every subsequent version.

Configuring the module system in Node.js: files with the .mjs extension are always treated as ES Modules. Files with the .cjs extension are always treated as CommonJS. Files with the .js extension use the format specified by the nearest package.json's type field — "type": "module" makes .js files ES Modules, and "type": "commonjs" (or no type field) makes .js files CommonJS.

The interoperability challenge is significant in 2026: ES Modules can import CommonJS modules using the default import syntax, but CommonJS modules cannot require() ES Modules (because require() is synchronous and ES Module loading is asynchronous). This creates a real-world problem when a package you depend on has migrated to pure ESM — your CommonJS project cannot require() it directly. Solutions include using dynamic import() (which returns a Promise) for importing ESM packages from CommonJS code, migrating your project to ESM, or using packages that maintain dual CJS/ESM distributions.

Question 5. What is the Node.js Cluster module and how does it improve production performance?

Node.js runs on a single thread and therefore uses only one CPU core regardless of how many cores the server has. On a modern server with 8, 16, or 32 cores, a single Node.js process uses a fraction of available compute capacity. The Cluster module solves this by allowing you to create multiple worker processes that all share the same server port and distribute incoming connections among themselves.

The Cluster module works by forking the main Node.js process into multiple worker processes using child_process.fork(). The primary process (the master) does not handle requests — it manages the worker processes, distributing incoming connections among them using a round-robin algorithm (on Linux) or the operating system's native load distribution (on Windows). Each worker process is an independent Node.js process with its own memory heap, its own V8 instance, and its own event loop. Workers do not share memory with each other or with the primary process.

The number of worker processes should typically equal the number of CPU cores on the server, maximizing CPU utilization. Using os.cpus().length provides the core count dynamically. The primary process monitors workers and respawns them if they crash, providing resilience against worker failures — when a worker crashes, the primary detects it through the exit event and forks a replacement, maintaining the cluster's capacity.

In production, the Cluster module is typically managed through PM2 rather than custom cluster code. PM2's cluster mode (pm2 start app.js -i max) automatically creates one worker per CPU core, manages worker lifecycle including automatic restart on crash, provides zero-downtime restarts when deploying new code (restarting workers one at a time so the server remains available throughout the deployment), and offers monitoring and logging capabilities. PM2 cluster mode is the production-standard approach to Node.js multi-core utilization in 2026.

Express.js Middleware and Routing Interview Questions

Express.js middleware and routing architecture questions assess your understanding of how Express handles requests and how to structure a production-grade Express application. These questions are asked at all experience levels but go deeper at senior interviews.

Question 6. What is Express middleware and how does the middleware pipeline work?

Express middleware are functions that have access to the request object, the response object, and the next middleware function in the application's request-response cycle. They form a pipeline where each middleware function can examine and modify the request and response, execute any code, end the request-response cycle by sending a response, or pass control to the next middleware by calling next().

The middleware pipeline executes in the order that middleware functions are registered with app.use() or on specific routes. When a request arrives, Express passes it through each registered middleware in sequence. If a middleware sends a response (using res.send(), res.json(), res.end()), the pipeline stops — subsequent middleware do not execute. If a middleware calls next(), Express passes the request to the next middleware in the pipeline. If a middleware neither sends a response nor calls next(), the request hangs indefinitely.

Application-level middleware is registered with app.use() and applies to all routes or to routes matching a specified path prefix. Router-level middleware is registered on an express.Router() instance and applies only to routes on that router. Error-handling middleware has four parameters — err, req, res, next — and is only invoked when next(error) is called with an error argument or when a synchronous error is thrown in a route handler.

The practical importance of middleware ordering is a key interview point. CORS middleware must be registered before route handlers, or OPTIONS preflight requests are rejected before they reach the route handler that would have handled them. Body parsing middleware must be registered before route handlers that read req.body, or req.body is undefined. Authentication middleware must be registered before route handlers that require authentication, or unauthenticated requests reach protected endpoints. Rate limiting middleware should be registered before expensive route handlers, or rate-limited requests still consume database connections and compute resources before being rejected.

Question 7. How do you implement global error handling in Express.js?

Error handling in Express requires understanding how errors flow through the middleware pipeline and how to catch errors from both synchronous and asynchronous route handlers.

Synchronous errors thrown in route handlers are automatically caught by Express and forwarded to error-handling middleware. If a route handler throws an Error synchronously, Express catches it and calls next(error) internally. Asynchronous errors — errors thrown inside async functions or Promise rejections — require explicit handling because Express cannot automatically catch them in Express 4. In an async route handler, you must wrap the body in a try-catch and call next(error) in the catch block. Express 5 (available as a release candidate in 2026) automatically handles Promise rejections from async route handlers, forwarding them to error-handling middleware without explicit try-catch.

A practical pattern for Express 4 is to create an asyncHandler wrapper — a higher-order function that wraps an async route handler and attaches a .catch(next) to the returned Promise. Applying asyncHandler to every async route handler eliminates the need for try-catch in every handler while still correctly forwarding all rejections to error-handling middleware.

The global error handler is the last middleware registered in the Express application, after all routes and after all other middleware. It has the four-parameter signature (err, req, res, next) that distinguishes error-handling middleware from regular middleware. The global error handler should log the full error with stack trace for server-side debugging, determine the appropriate HTTP status code from the error type or the error's status property, and send a clean, structured JSON error response to the client that does not expose internal implementation details or stack traces.

Custom error classes extend the native Error class to add a statusCode property and isOperational property. Operational errors are expected application errors like validation failures, authentication failures, and not-found resources — these generate meaningful error responses. Non-operational errors are unexpected bugs — these should generate a generic 500 Internal Server Error response and trigger alerts for the engineering team.

Question 8. What is the difference between app.use() and app.get() in Express?

app.use() registers middleware that applies to all HTTP methods for the specified path prefix or for all paths if no path is specified. When a path is provided to app.use(), it matches any request whose URL starts with that path — not just exact matches. A route registered with app.use('/api') matches requests to /api, /api/users, /api/products/123, and any other URL beginning with /api.

app.get() registers a route handler specifically for HTTP GET requests at an exact path. It matches only GET requests and only at the specified path (with route parameters if defined). A route registered with app.get('/api/users') only matches GET requests to exactly /api/users and does not match /api/users/123 (unless you define a separate route with that pattern).

The same distinction applies to app.post(), app.put(), app.patch(), app.delete(), and app.all(). app.all() is similar to app.use() in that it matches all HTTP methods, but unlike app.use() it requires an exact path match rather than a prefix match.

The practical implication for API design: use app.use() for middleware (authentication, logging, rate limiting, body parsing, CORS) that should apply to multiple routes or all routes. Use app.get(), app.post(), etc., for specific route handlers where the HTTP method is semantically meaningful. Use Express Router to group related routes and apply router-specific middleware — a router for /api/users routes with user-specific authentication middleware, a router for /api/admin routes with admin authorization middleware, and so on.

REST API Design and Best Practices Interview Questions

REST API design questions assess your ability to build APIs that are consistent, predictable, and easy for clients to consume. Companies ask these at all seniority levels but with increasing depth at senior and lead interviews.

Question 9. What are the REST API design principles and how do you apply them in Express?

REST (Representational State Transfer) is an architectural style for designing network-based APIs. It is defined by six constraints whose consistent application produces APIs that are intuitive to use, scalable, and maintainable over time.

The stateless constraint requires that each request from client to server contain all information necessary to understand and process the request. The server does not store any session state between requests. Authentication tokens, pagination parameters, and any other state required for the request must be included in the request itself. In Express, statelessness is implemented by using JWT tokens rather than server-side sessions, by accepting pagination parameters as query parameters rather than maintaining cursor state on the server, and by avoiding any request processing logic that depends on previous requests.

The uniform interface constraint requires consistent use of HTTP methods semantically. GET retrieves resources without side effects. POST creates new resources. PUT replaces a resource completely. PATCH updates a resource partially. DELETE removes a resource. In Express route design, consistently using these methods according to their semantic meaning — not using GET for operations that have side effects, not using POST for read operations — produces an API that developers can use intuitively without consulting documentation for every endpoint.

Resource-based URLs use nouns to identify resources rather than verbs to describe operations. The URL identifies what the resource is, and the HTTP method describes what to do with it. A well-designed URL is /api/users/123/orders (retrieving orders for a specific user), not /api/getUserOrders?userId=123 (verb-based URL that conflates the operation with the identifier).

Consistent response structure means every successful response has the same top-level structure, every error response has the same error structure, and status codes are used correctly. A 200 OK for successful GETs, 201 Created for successful POSTs with the created resource in the response body, 204 No Content for successful DELETEs, 400 Bad Request for validation errors with field-level error details, 401 Unauthorized for missing or invalid authentication, 403 Forbidden for authenticated users accessing resources they do not have permission to access, and 404 Not Found for requested resources that do not exist.

Question 10. How do you implement API versioning in Node.js and Express?

API versioning allows you to introduce breaking changes to your API without forcing all clients to update simultaneously. It is a critical production API concern because APIs are consumed by multiple clients — web applications, mobile apps, third-party integrations — that cannot all update in perfect synchrony.

URL versioning includes the version number in the URL path: /api/v1/users, /api/v2/users. This is the most explicit and most widely used versioning strategy because the version is visible in every request, easy to route at the server level, and simple for clients to understand and use. In Express, URL versioning is implemented by creating separate routers for each API version and mounting them at their respective version paths. Route handlers for v1 and v2 can share utility functions and data access logic while having different request validation, response shapes, or business logic.

Header versioning uses a custom HTTP header to specify the API version: Accept-Version: 2 or API-Version: 2026-01-01. Header versioning keeps URLs clean but requires clients to set the header correctly and makes the version invisible in URLs, which complicates logging, debugging, and caching. Express middleware that reads the version header and routes to the appropriate handler implements header versioning.

Query parameter versioning includes the version as a query parameter: /api/users?version=2. This approach is less recommended because query parameters are conventionally used for filtering and pagination, and because version parameters get mixed into query strings in ways that complicate caching.

The production practice recommendation for 2026 is URL versioning for public APIs consumed by external clients, and header versioning for internal APIs consumed only by your own frontend applications where you control all clients. Maintain at least the current and one previous version simultaneously, communicate deprecation timelines at least six months in advance, and provide migration guides when breaking changes require client updates.

Question 11. How do you implement pagination in a REST API and what are the trade-offs between approaches?

Pagination is essential for any API endpoint that returns collections of resources. Without pagination, a single request for all users in a database with millions of records would consume enormous database resources, generate a massive response payload, and time out before completing. There are three main pagination strategies, each with different trade-offs.

Offset pagination uses page and limit (or offset and limit) query parameters. The client requests page 2 with 20 items per page, the server queries the database with LIMIT 20 OFFSET 20, and returns the results along with the total count and metadata for calculating the total number of pages. Offset pagination is easy to implement, allows jumping to any page directly, and is familiar to users and clients. The trade-off is performance at large offsets — a query with LIMIT 20 OFFSET 10000 requires the database to scan and discard the first 10,000 rows before returning the 20 needed rows. Additionally, offset pagination has consistency issues when the underlying data changes between page requests — items inserted before the current offset cause the next page to show items already seen on the previous page.

Cursor-based pagination uses an opaque cursor (typically the ID or a combination of fields from the last item seen) rather than an offset. The first request returns the first page and a nextCursor value. Subsequent requests include the cursor as a parameter, and the query uses WHERE id > cursor LIMIT 20 (for forward pagination by ID). Cursor-based pagination is consistent regardless of concurrent data changes (new items inserted before the cursor do not affect subsequent pages), and it performs efficiently at any position in the dataset because the database uses an index to find the cursor position rather than scanning rows. The trade-off is that you cannot jump to an arbitrary page — you must traverse the dataset sequentially, and the cursor may be meaningless to clients.

Keyset pagination is similar to cursor-based but uses explicit, human-readable query parameters based on the sort fields rather than an opaque cursor. It shares the performance and consistency benefits of cursor-based pagination while being slightly more transparent to API consumers.

For 2026 production APIs, cursor-based or keyset pagination is recommended for any collection endpoint where the dataset is large or growing. Offset pagination is acceptable for small, stable datasets where jumping to arbitrary pages is genuinely needed and performance at high offsets is not a concern.

GraphQL with Node.js Interview Questions

GraphQL is asked in senior backend interviews at companies that have adopted it as an alternative to REST, and increasingly as a way to evaluate whether candidates understand modern API design philosophies beyond the REST default.

Question 12. What is GraphQL and how does it differ from REST?

GraphQL is a query language for APIs and a runtime for executing those queries, developed by Facebook and open-sourced in 2015. Rather than defining multiple URL-based endpoints each returning a fixed data structure, GraphQL exposes a single endpoint that accepts structured queries describing exactly what data the client needs, and returns exactly that data — no more, no less.

The fundamental difference from REST is the client's control over the data shape. In a REST API, the server defines the shape of each endpoint's response. A GET /users/123 endpoint might return the user's ID, name, email, profile picture URL, created date, last login date, preferences, subscription status, and any other fields the server decided to include. A mobile client that only needs the name and profile picture for displaying a user avatar receives all the other fields too — wasting bandwidth and requiring the client to filter out unused data. This is over-fetching. Conversely, a client that needs data from multiple related resources (a user, their recent orders, and each order's items) must make multiple sequential requests — under-fetching.

GraphQL eliminates both problems. The client sends a query specifying exactly which fields of which types it needs. The server executes the query and returns precisely those fields. A query for user name and profile picture returns only those fields. A query that needs user data plus their orders in a single response can request all of it in one query, and the server returns it all in one response.

Other key differences: REST uses different URLs for different resources. GraphQL uses a single endpoint for all queries and mutations. REST uses HTTP methods (GET, POST, PUT, DELETE) to distinguish operations. GraphQL uses query (for reads) and mutation (for writes) operation types in the query document. REST responses are defined by the server and documented separately. GraphQL has a type system and schema that self-documents the API — clients can introspect the schema to discover all available types, queries, and mutations. REST has native HTTP caching through URL-based cache keys. GraphQL POST requests are not cached by default, requiring client-side caching solutions.

Question 13. How do you implement a GraphQL server with Node.js and what is the N+1 problem?

In Node.js, GraphQL servers are most commonly built using Apollo Server or the more lightweight graphql-yoga. Both integrate with Express (or other Node.js frameworks) and provide the runtime for parsing GraphQL queries, executing resolvers, and formatting responses.

Setting up a GraphQL server requires three elements. The schema definition describes all the types in your API, all available queries (reads), all available mutations (writes), and all available subscriptions (real-time events). The resolvers are functions that provide the data for each field in the schema — a resolver for the users query fetches the list of users, a resolver for the name field on the User type returns the user's name property. The server configuration combines the schema and resolvers into a running GraphQL endpoint.

The N+1 problem is the most important GraphQL performance problem and is asked in virtually every senior GraphQL interview. It occurs when a query requests a list of items and then accesses a related field on each item, causing one database query to fetch the list and N additional queries (one per item) to fetch the related data. A query for 100 users and each user's recent orders would naively execute 1 query for users and 100 separate queries for orders — 101 total database queries for what should be expressed in one or two queries.

The solution to the N+1 problem in GraphQL is the DataLoader pattern. DataLoader is a utility that batches multiple individual data requests into a single request to the data source. Instead of each resolver calling the database individually when a related field is requested, the resolver adds its request to a DataLoader batch. DataLoader collects all requests made during the current tick, executes a single batched database query (SELECT WHERE id IN (id1, id2, ... idN)), and returns the individual results to each resolver. The 100 individual order queries become 1 batched query. DataLoader also caches results within a request, so if the same entity is requested multiple times in a single GraphQL query (a user who appears in multiple parts of the response), the database is only hit once.

JWT Authentication and Security Interview Questions

Authentication and security questions are always present in senior backend interviews because security vulnerabilities in production APIs have direct business impact. These questions go deep into implementation details that developers who have only used authentication libraries without understanding them cannot answer.

Question 14. How does JWT authentication work and what are access tokens and refresh tokens?

JSON Web Tokens (JWT) are a compact, URL-safe format for transmitting claims between parties as a JSON object. A JWT consists of three parts separated by dots: the header (algorithm and token type, Base64URL encoded), the payload (claims — user ID, roles, expiry, and any other data, Base64URL encoded), and the signature (cryptographic verification of the header and payload using the server's secret key or private key).

The JWT authentication flow works as follows. The client sends credentials (username and password) to the login endpoint. The server validates the credentials against the database, checking the password hash. If valid, the server generates two tokens: an access token with a short expiry (15 minutes to 1 hour) containing the user's ID and roles, and a refresh token with a long expiry (7 to 30 days) that is stored in the database associated with the user and device. The server sends both tokens to the client. The client stores the refresh token in an httpOnly cookie (inaccessible to JavaScript, protected from XSS) and the access token in memory (or a non-httpOnly cookie). The client includes the access token in the Authorization header (Bearer [token]) of every API request. The server validates the access token's signature and expiry on every protected request — no database lookup required.

When the access token expires, the client sends the refresh token to the token refresh endpoint. The server validates the refresh token against the stored hash in the database (not against a signature alone), generates a new access token, rotates the refresh token (generates a new refresh token and invalidates the old one), and returns both to the client.

Refresh token rotation is the security mechanism that makes refresh tokens safer. When a refresh token is used, it is immediately invalidated and a new one is issued. If an attacker steals a refresh token and uses it, the legitimate client's next use of the old token fails, alerting the system that the refresh token was compromised — the server can invalidate all of the user's refresh tokens and force re-authentication. Without rotation, a stolen refresh token gives an attacker long-lived access that is difficult to detect.

Question 15. What are common JWT security vulnerabilities and how do you prevent them?

JWT security vulnerabilities are a critical senior interview topic because JWT is widely used and widely misimplemented. Understanding the vulnerabilities and their mitigations demonstrates that you build secure systems rather than just functional ones.

The algorithm confusion attack is one of the most dangerous JWT vulnerabilities. JWT headers specify the algorithm used to sign the token. A vulnerable server that accepts the algorithm from the JWT header without validation can be tricked into accepting a token signed with the none algorithm (no signature at all) or with an algorithm the server did not intend to use. Prevention: always specify the allowed algorithms explicitly when verifying tokens and never trust the algorithm from the token header. Using a JWT library correctly — passing the expected algorithm explicitly to the verify function — prevents this attack.

The secret exposure risk occurs when JWT secrets are weak or stored insecurely. A JWT signed with a weak secret (short, dictionary word, or predictable value) can be brute-forced by an attacker who intercepts tokens. Prevention: use cryptographically random secrets of at least 256 bits. Store secrets in environment variables or a secrets manager, never in source code or version control. For high-security applications, use asymmetric signing (RS256 with a private key for signing and a public key for verification) rather than symmetric signing (HS256 with a shared secret).

Storing tokens in localStorage exposes them to XSS attacks. Any malicious JavaScript that runs in the browser can read localStorage and steal all stored tokens. Prevention: store refresh tokens in httpOnly, Secure, SameSite=Strict cookies that JavaScript cannot access. Store access tokens in memory (JavaScript variables) that are lost on page refresh but cannot be stolen by XSS. Accept the trade-off of requiring a token refresh on page load for the security benefit of XSS-resistant token storage.

Missing token revocation means that a stolen access token remains valid until its expiry with no way to invalidate it. If a user changes their password or reports a stolen device, all outstanding access tokens for that user should be invalidated. Prevention: maintain a token blocklist in Redis for revoked access tokens, checking every incoming access token against the blocklist before processing the request. The blocklist only needs to hold revoked tokens until their natural expiry — entries can be automatically expired by Redis TTL. This approach preserves the stateless nature of JWT validation while adding revocation capability.

Question 16. How do you implement role-based access control (RBAC) in Express?

Role-Based Access Control (RBAC) is the mechanism for controlling which users can access which resources and perform which operations based on their assigned roles. Implementing RBAC correctly in Express requires both the authentication layer (verifying who the user is) and the authorization layer (verifying what the user is allowed to do) to be properly separated and consistently applied.

The JWT-based RBAC implementation stores the user's roles in the JWT payload at login time. When the user authenticates, the server fetches the user's roles from the database and includes them in the access token payload (for example, roles: ["admin", "editor"]). Subsequent API requests include the JWT, and the authentication middleware decodes the token and attaches the user object (including roles) to req.user. This makes the user's roles available to all subsequent middleware and route handlers without additional database queries for every request.

Authorization middleware is implemented as reusable functions that check req.user.roles against the required roles for a specific route. An authorize middleware factory accepts an array of allowed roles and returns a middleware function that checks whether the authenticated user has at least one of the required roles. If the user has a required role, the middleware calls next() to proceed to the route handler. If not, it responds with 403 Forbidden. This factory pattern produces reusable, composable authorization middleware that can be applied to individual routes or to entire routers.

Permission-based access control (PBAC) is a more granular extension of RBAC where instead of (or in addition to) checking roles, you check specific permissions like users:read, users:write, orders:delete. Permissions are assigned to roles, and roles are assigned to users. Checking permissions rather than roles directly makes authorization logic more expressive and more maintainable as the application grows and role-permission mappings evolve without requiring changes to route handlers.

Node.js Performance Optimization Interview Questions

Performance questions are the distinguishing topic of senior Node.js interviews. They separate developers who build systems that work from developers who build systems that work well under production load.

Question 17. How do you identify and fix memory leaks in Node.js?

Memory leaks in Node.js occur when objects are allocated in memory but never garbage collected because references to them are accidentally maintained, causing the process's memory usage to grow continuously until the process runs out of memory and crashes or becomes extremely slow due to garbage collection pressure.

The most common causes of memory leaks in Node.js production applications are: global variables that accumulate data indefinitely (adding items to a global array or object without ever removing them), event listeners that are added to EventEmitter instances but never removed (especially common when listeners are added inside request handlers without cleanup), closures that inadvertently capture large objects in their scope (a callback that closes over a large response object preventing it from being garbage collected), and unbounded caches (caches that grow indefinitely without eviction policies).

Diagnosing memory leaks requires heap analysis tools. The --inspect flag enables Node.js's V8 Inspector and Chrome DevTools connection. Take heap snapshots at regular intervals using the Memory tab in Chrome DevTools (connected to the running Node.js process). Compare snapshots to identify objects that accumulate between snapshots — objects whose count increases consistently between snapshots are the likely leak source. The clinic.js tool (specifically clinic heap) provides automated heap analysis without requiring manual DevTools usage. The --expose-gc flag enables programmatic garbage collection triggering for controlled leak testing.

Fixing identified leaks requires removing the unintended reference that prevents garbage collection. For event listener leaks, call emitter.removeListener() or emitter.off() in cleanup code (in the close event handler, in the request's finish event, or in response to component unmounting in server-side rendering). For global accumulation, implement LRU (Least Recently Used) cache with maximum size using the lru-cache package. For closure leaks, restructure code to avoid capturing large objects in long-lived closures or explicitly set large references to null when they are no longer needed.

Question 18. How do you implement caching in a Node.js API and what caching strategies do you use?

Caching is one of the most impactful performance optimizations available for Node.js APIs. By serving responses from cache instead of re-computing them from the database, caching reduces database load, reduces response time from hundreds of milliseconds to single-digit milliseconds, and enables APIs to handle significantly more concurrent requests with the same infrastructure.

In-memory caching using Node.js variables is the fastest possible cache but does not survive process restarts and is not shared between multiple Node.js instances (workers in a cluster or multiple pods in Kubernetes). Use in-memory caching with the node-cache package for application configuration that is read frequently and changes very rarely, and for other data where per-instance caching is acceptable.

Redis caching is the standard distributed cache for production Node.js applications. Redis is an in-memory data store with optional persistence that is accessible to all Node.js instances simultaneously. Responses cached in Redis survive process restarts, are shared across all application instances, and can be managed centrally (inspecting, invalidating, and monitoring cached data without restarting the application). Use Redis for API response caching (caching the full JSON response for frequently-requested endpoints), session storage, rate limiter counters, and any data that must be consistent across multiple application instances.

HTTP response caching uses Cache-Control headers to instruct browsers and CDNs to cache API responses. For public, non-personalized API responses, setting Cache-Control: public, max-age=300 tells CDNs and browsers to cache the response for 5 minutes. For personalized responses, Cache-Control: private, max-age=60 caches the response only in the user's browser. For responses that must never be cached, Cache-Control: no-store prevents all caching. HTTP response caching is the most scalable caching strategy because it shifts cache serving to the CDN edge, reducing load on origin servers entirely for cached responses.

Cache invalidation — knowing when to remove or update cached data — is the hard part of caching. Tag-based invalidation groups cached entries by the data they depend on and invalidates all entries with a specific tag when that data changes. Time-based invalidation (TTL) automatically expires cache entries after a defined duration, accepting that cached data may be slightly stale. Event-driven invalidation explicitly removes or updates cache entries in response to data change events, maintaining strong consistency at the cost of more complex invalidation logic.

Question 19. What is connection pooling and how do you configure it in Node.js?

Connection pooling is the practice of maintaining a pool of pre-established database connections that can be reused across multiple requests, rather than creating and destroying a new database connection for each request. Establishing a database connection involves a TCP handshake, authentication, session setup, and protocol negotiation — a process that typically takes 50 to 150 milliseconds. For an API handling 100 requests per second, creating a connection per request adds 5 to 15 seconds of connection overhead every second, which is clearly unsustainable.

A connection pool maintains a set of connections that are established once at application startup and reused across requests. When a request needs a database connection, it borrows one from the pool, uses it for the duration of the database operation, and returns it to the pool when done. If all connections are in use when a new request arrives, the request waits in a queue until a connection becomes available or a timeout occurs.

PostgreSQL connection pooling with pg and pg-pool requires configuring the maximum number of connections in the pool, the minimum number of idle connections maintained, the connection timeout (how long to wait for a connection before throwing an error), and the idle timeout (how long an idle connection is kept before being closed). The maximum pool size should be determined by the database server's connection limit divided by the number of application instances — a PostgreSQL instance configured for 100 connections hosting 4 Node.js application instances should have each instance configured with a maximum pool size of 25.

MongoDB connection pooling with Mongoose is configured through the options passed to mongoose.connect() — specifically the maxPoolSize option (default 10, appropriate for most applications but may need increasing for high-throughput services), the serverSelectionTimeoutMS (timeout for selecting a MongoDB server), and the socketTimeoutMS (timeout for socket inactivity). Mongoose manages the pool automatically once configured, reusing connections across all model operations without any additional code.

For high-throughput services, connection pool exhaustion — all connections in use with new requests waiting — is a significant performance bottleneck. Monitor pool metrics (active connections, waiting requests, connection wait time) using Prometheus metrics exposed by your database driver. Set appropriate connection timeouts so requests fail fast with a clear error rather than queuing indefinitely when the pool is exhausted. Consider PgBouncer (for PostgreSQL) as a connection pooler in front of the database if application-level pooling is insufficient for the connection load.

Question 20. How do you implement rate limiting in Express and why is it important for production APIs?

Rate limiting restricts how many requests a client can make to your API within a given time window. Without rate limiting, a single misbehaving client — a buggy application with a retry loop, a user running automated scripts, or a malicious attacker attempting a denial of service — can consume all available server resources, degrading or eliminating service for all other users.

The express-rate-limit package provides the most widely used rate limiting solution for Express. It accepts configuration for the time window duration (in milliseconds), the maximum number of requests allowed per window, the response message sent when the limit is exceeded, and whether to include rate limit headers in responses (showing the client their current limit, remaining requests, and reset time). Adding the middleware globally limits all endpoints, or it can be applied to specific routes that need tighter limits.

Redis-backed rate limiting is required for production deployments with multiple Node.js instances. The default in-memory store counts requests within each process independently — a user making 100 requests that are distributed across 4 Node.js instances would only be counted as making 25 requests in each process's store, allowing them to make 400 requests total instead of the intended 100. Using rate-limit-redis as the store for express-rate-limit shares the counter across all instances through Redis, enforcing the limit correctly regardless of how requests are distributed.

Tiered rate limiting applies different limits to different route categories. Authentication endpoints (login, password reset, account registration) should have very strict limits (5 to 10 requests per 15 minutes) to prevent brute-force attacks. Public, read-only API endpoints can have more generous limits (1,000 requests per hour per IP). Authenticated API endpoints can have per-user limits rather than per-IP limits, allowing authenticated users their own bucket rather than sharing one with all users behind a shared NAT IP. Administrative endpoints should be either rate-limited very strictly or accessed only through allowlisted IP ranges.

Frequently Asked Questions

What is the most important Node.js concept to understand for interviews in 2026?

The event loop is consistently the most important Node.js concept tested in interviews at all experience levels. Interviewers ask about it directly in fundamental questions and test understanding of it indirectly in questions about async patterns, performance problems, and blocking code. A developer who truly understands the event loop — the phases, the priority of microtasks, the impact of blocking code, and the purpose of Worker Threads — can reason through virtually any Node.js performance or concurrency question. Study the event loop until you can explain it clearly without notes, draw the phases on a whiteboard, and give examples of code that blocks the event loop and code that correctly uses async patterns to avoid blocking.

How deeply should freshers know GraphQL for Node.js interviews?

For freshers applying to junior Node.js backend roles, awareness of what GraphQL is, how it differs from REST conceptually, and what problems it solves is sufficient. Freshers are not expected to have production GraphQL implementation experience. For mid-level and senior Node.js interviews at companies that use GraphQL, deep knowledge is expected: schema design, resolver implementation, the N+1 problem and DataLoader solution, authentication in GraphQL context, and performance considerations. If a job description specifically mentions GraphQL, treat it as a primary interview topic regardless of experience level.

What salary can a Node.js backend developer expect in 2026?

In India, fresher Node.js backend developers earn between 4 and 7 LPA. Mid-level developers with 2 to 4 years of Node.js experience earn between 10 and 20 LPA. Senior developers with 5 or more years, especially those with microservices, GraphQL, and cloud platform experience, earn between 22 and 45 LPA at top product companies. In the United States, entry-level Node.js developers earn between 80,000 and 110,000 USD annually. Mid-level earns between 120,000 and 160,000 USD. Senior backend engineers with Node.js expertise earn between 160,000 and 220,000 USD at top product companies.

Is knowing TypeScript mandatory for Node.js interviews in 2026?

TypeScript knowledge is expected for senior Node.js roles at product companies in 2026. Most enterprise Node.js codebases have migrated to TypeScript or are actively migrating. TypeScript provides type safety that catches a significant category of bugs at compile time, improves IDE support and code navigation, and makes large Node.js codebases more maintainable. For junior roles, TypeScript familiarity is a differentiator but not always strictly required. For mid-level and senior roles, being able to write TypeScript interfaces and types, use generics, and understand TypeScript's structural type system is expected. Learn TypeScript early in your Node.js journey rather than treating it as an advanced topic.

What projects should I build to prepare for Node.js and Express interviews?

Build projects that demonstrate all the concepts covered in this guide through real implementation. A REST API with full CRUD operations, JWT authentication with refresh token rotation, rate limiting, pagination, and proper error handling demonstrates the core backend API skills. A GraphQL API with a type schema, resolvers, DataLoader for N+1 prevention, and authentication demonstrates modern API design knowledge. A microservice system with two or three services communicating via REST or message queues demonstrates distributed systems awareness. Deploy all projects to a cloud provider (AWS, GCP, or Azure) with proper environment variable management and write basic unit and integration tests. Projects on GitHub with clear READMEs explaining the architecture choices are far more impressive in an interview than projects that only exist locally.

Conclusion

Node.js and Express backend development interviews in 2026 test a broad range of knowledge — from the internal mechanics of the event loop through production API design, authentication security, GraphQL implementation, and performance optimization under real-world load. The developers who pass these interviews at top product companies are those who understand not just how to use Node.js and Express APIs but why those APIs work the way they do, what trade-offs each design decision involves, and how to diagnose and solve the production problems that arise in real systems.

Every question and answer in this guide is grounded in real production experience and real interview feedback from Node.js developers at product companies in 2026. Study the concepts, build the projects, understand the trade-offs, and practice explaining your reasoning clearly. That combination — depth of knowledge, practical experience, and clear communication — is what gets senior Node.js backend developers hired.

Ready to build full-stack JavaScript applications with Node.js, Express, MongoDB, and React with expert guidance and placement support? JustAcademy's MERN Stack Developer Bootcamp covers the complete backend and frontend stack with real-world projects and 100% placement support.

Related Bootcamps

MEAN Stack Developer Bootcamp
Node JS course
Full Stack QA Automation Bootcamp
Full Stack Mobile App Development Bootcamp (Flutter, Node.js, MongoDB, Express)

JustAcademy | 1201, 12th Floor, Star Plaza, Borivali East, Mumbai 400066 | +91 99871 84296 | www.justacademy.co

Connect With Us
whatsapp