Interview questions · free

Node.js Developer interview questions and answers

26 questions Node.js Developer interviews in India ask, grouped by the 5 skills they test, with a short model answer for each. Then the HR round every job has. Try each one out loud before you open the answer.

  • 26 questions
  • 5 skills
  • 12 for freshers
  • Updated 30 Oct 2026

First mock interview free: 3 questions, a score and feedback on each · No card needed

Answer out loud first. Read the question, say your answer as you would in the room, then open the model answer and compare. Interviewers ask follow-ups, so learn the reasoning, not the words.

Node.js · Core skill

Event loop and runtime

Node.js interviews go deeper than JavaScript basics: you're expected to explain the event loop, streams and how a single thread serves many users without blocking.

Node.js is single-threaded. How can it handle thousands of requests at once?Fresher

Your JavaScript runs on one main thread, but Node doesn't wait for slow operations. When code reads a file, queries a database or calls an API, Node hands the work to the operating system or to libuv's small thread pool and moves on to the next request. When the result is ready, its callback is queued, and the event loop runs it when the main thread is free.

So one thread can serve many requests as long as each spends most of its time waiting for I/O, which is typical of APIs. The weak spot is CPU-heavy work, like resizing images or a long loop: it blocks the thread, and every other request waits. Interviewers expect you to mention that limit.

What is the difference between CommonJS and ES modules in Node? How does Node decide which one a file uses?Fresher

CommonJS uses require() and module.exports, loads modules synchronously, and was Node's original system. ES modules use import and export, are the JavaScript standard, load asynchronously and allow top-level await. Node treats .mjs files as ES modules and .cjs files as CommonJS; for .js files it follows the nearest package.json, where "type": "module" means ES modules and anything else means CommonJS.

Practical differences: ES modules have no __dirname or require (use import.meta.url, or import.meta.dirname in newer versions), and imports of local files need the file extension. An ES module can import a CommonJS one; going the other way traditionally needed a dynamic import(). New projects usually choose ES modules.

In a CommonJS script, what is the output order: console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); process.nextTick(() => console.log('D')); console.log('E');1-6 years

A, E, D, C, B. The synchronous code runs first, printing A and E. When the script finishes, and after each callback, Node empties two queues before moving on: first the process.nextTick queue (D), then the promise microtask queue (C). Only then does the event loop continue to its phases, starting with timers, so the setTimeout callback prints B.

The loop's main phases are timers, pending callbacks, poll (waiting for I/O), check (setImmediate) and close callbacks. Mention one catch: setTimeout(0) and setImmediate called from the main script can run in either order, but inside an I/O callback setImmediate always runs first. Recursive nextTick calls can starve I/O.

You need to process a 2 GB CSV file in a Node service. Why is fs.readFile a bad idea, and what would you use?1-6 years

fs.readFile loads the whole file into memory before you can use it: gigabytes of memory per request, which can crash the process, and nothing happens until it has all been read. Use streams instead: fs.createReadStream reads the file in small chunks, so memory stays low and processing starts at once. Pass it through a CSV parser or the readline module to handle one row at a time, and insert into the database in batches.

Connect streams with stream.pipeline (or its promise version), because it passes errors along and closes everything if one step fails. Streams also handle backpressure: when a slow destination can't keep up, the source pauses instead of filling memory. The same applies to uploads and downloads.

An endpoint that generates PDF reports makes the whole API slow for every user. Why, and how would you fix it?1-6 years

PDF generation is CPU-heavy and it's running on the main thread, so while one report is built the event loop can't serve any other request. Confirm it with a profiler or by measuring event loop delay.

Fixes depend on the scale. Move the work to worker_threads, which run JavaScript on separate threads, ideally through a small pool such as Piscina. For long jobs, it's better to put the job on a queue (BullMQ with Redis, for example), let a separate worker process build the PDF, and notify the user or offer a download when it's ready. Running several Node processes with cluster or PM2 uses more CPU cores and adds capacity, but one heavy request still blocks its own process.

Node.js · Core skill

Express and REST APIs

Most Node.js backend roles build REST APIs with Express or a similar framework, so interviewers check routing, middleware, error handling and how you keep a growing API safe.

What is the difference between req.params, req.query and req.body in Express?Fresher

req.params holds the named parts of the route path: for the route /users/:id and the URL /users/42, req.params.id is '42'. req.query holds the query string: /products?category=books&page=2 gives req.query.category and req.query.page. req.body holds the data sent in the request body, usually with POST, PUT or PATCH, and it stays undefined unless a body parser such as express.json() or express.urlencoded() runs first.

All of these come from the client, mostly as strings, so convert and validate them: '42' is not the number 42. Use params to identify a resource, the query for filtering, sorting and paging, and the body for the data you create or update.

How does error handling work in Express? What happens if an async route handler throws?Fresher

Express recognises error-handling middleware by its four arguments, (err, req, res, next). Put it after all the routes; when a route calls next(err) or throws synchronously, Express skips to it, and it can log the error and send a consistent JSON response with the right status code, hiding stack traces in production.

Async code is the trap. In Express 4, a rejected promise in an async handler isn't caught: the request hangs and you get an unhandled rejection. You have to wrap handlers in try/catch and call next(err), or use a wrapper such as express-async-handler. Express 5 passes rejected promises to the error handler automatically. Also add a 404 handler before the error handler for unknown routes.

How would you structure a large Express codebase so it stays maintainable?1-6 years

Split it into layers by responsibility. Routes, with one express.Router per feature (orders, users, payments), only map URLs and middleware to controllers. Controllers read the request, call a service and send the response. Services hold the business rules and know nothing about Express, so a queue worker can reuse them and they're easy to unit test. A data layer, repositories or the ORM's models, talks to the database.

Keep configuration in one module that reads environment variables and fails at start-up if one is missing, centralise validation and error handling in middleware, and log with a structured logger such as pino. Group files by feature as the app grows. The aim: adding a feature touches one folder.

Your Express API is public. How would you protect it from abuse and common attacks?1-6 years

Several layers. Rate limiting, for example with express-rate-limit per IP or per user, with stricter limits on login and OTP routes; behind a load balancer, set trust proxy so the real client IP is used. Limit the request body size in express.json() and validate every input with a schema. Use helmet for security headers, and allow only your own origins in CORS.

Use parameterised queries or the ORM's methods to prevent SQL and NoSQL injection, never pass raw user input into query operators, and keep secrets in environment variables. Set timeouts on calls to other services, return generic error messages, and keep dependencies updated with npm audit. In-memory rate limits don't work across several servers, so use a shared store such as Redis.

JavaScript · Core skill

Async and promises

Almost every line of a Node.js service is asynchronous, so interviewers test promises and async/await closely, especially the mistakes that silently lose errors or run things in the wrong order.

What is callback hell? How do promises and async/await improve it?Fresher

Callback hell is code where each async step is a callback nested inside the previous one: read the user, then their orders, then each order's items, then send the response. It gets deeply indented, and error handling must be repeated at every level, which is easy to forget.

A promise represents a future result, so steps can be chained with .then() in a flat line, and one .catch() handles an error from any step. async/await is built on promises and lets you write the same flow like ordinary code, const user = await getUser(id), with try/catch for errors. For old callback-style APIs, util.promisify converts them, and most core modules now have promise versions, such as fs/promises.

What is the difference between Promise.all, Promise.allSettled, Promise.race and Promise.any?Fresher

All four take several promises that are already running and combine them. Promise.all resolves with every result, in order, but rejects as soon as any one rejects, so use it when you need all the results, like loading a user and their settings together. Promise.allSettled waits for all of them and reports each outcome, fulfilled or rejected, which suits sending ten notifications where some may fail. Promise.race settles with whichever finishes first, success or failure, and is often used to add a timeout. Promise.any resolves with the first success and rejects only if all fail.

A common mistake is awaiting independent calls one by one when Promise.all would run them in parallel.

This should save every order before responding, but the response goes out first: orders.forEach(async (o) => { await save(o); }); res.send('done'). Why, and how do you fix it?1-6 years

forEach doesn't know about promises: it calls the async callback for each order, ignores the promise each call returns, and finishes immediately. The await pauses only inside each callback, not the outer function, so res.send runs before any save completes, and a failed save becomes an unhandled rejection.

To save one after another, use a for...of loop with await inside. To save in parallel, map to promises and wait for all of them: await Promise.all(orders.map(o => save(o))). For a large list, limit how many run at once, with a small batching loop or a library such as p-limit, so you don't overload the database. map, filter and reduce with async callbacks have the same trap.

Your Node service calls a payment provider's API with fetch. What error handling would you add?1-6 years

First, fetch rejects only on network failures; a 400 or 500 response still resolves, so check response.ok or the status and handle errors explicitly. Second, add your own timeout, because fetch has no sensible one by default: pass signal: AbortSignal.timeout(5000) and handle the resulting error. Third, retry only what's safe: network errors and 5xx or 429 responses, with exponential backoff and a limit. Never retry a payment request unless the provider supports an idempotency key, or you might charge the customer twice.

Wrap the call in try/catch, log the request id and status but never card details or keys, and return a clear error to your own client. A circuit breaker helps during long outages.

What is an unhandled promise rejection in Node? What happens, and how should an app deal with errors nothing catches?1-6 years

It's a promise that rejects with no .catch() and no try/catch around its await, for example a database call started without await. In current Node versions an unhandled rejection crashes the process by default, just like an uncaught exception, and that's reasonable: the app may be in a broken state.

Prevent them: await or return every promise, and turn on a lint rule for floating promises. As a safety net, listen for process.on('unhandledRejection') and process.on('uncaughtException') to log the error with full details, then exit rather than carry on. Run the app under a process manager such as PM2, systemd or Kubernetes that restarts it, and handle SIGTERM to close the server and database connections cleanly.

Node.js

Authentication

A Node.js backend developer usually owns sign-up, login and access control for the API, so interviewers check password storage, token handling and route protection in detail.

How should you store users' passwords in the database?Fresher

Never store them as plain text, and never encrypt them, since anything that can be decrypted can be leaked. Store a hash made with a slow password-hashing algorithm such as bcrypt, scrypt or Argon2, which adds a random salt per user, so identical passwords give different hashes and precomputed tables are useless. Fast hashes like MD5 or SHA-256 are wrong for passwords, because attackers can try billions of guesses per second.

In Node with bcrypt: hash on sign-up with a cost factor such as 12, and on login use bcrypt.compare with the stored hash. Show the same message for a wrong email and a wrong password, and rate-limit login attempts. Raise the cost factor over time as hardware gets faster.

How would you write Express middleware that checks a JWT and restricts some routes to admins?1-6 years

An authenticate middleware reads the Authorization header, expects the word Bearer followed by the token, and verifies it with jwt.verify, passing the secret or public key and the allowed algorithm explicitly, such as algorithms: ['HS256'], so an attacker can't switch it. If the token is missing, invalid or expired, return 401; otherwise set req.user from its claims and call next().

For roles, a factory such as requireRole('admin') returns middleware that checks req.user.role and returns 403 if it doesn't match. Chain them per route: router.delete('/users/:id', authenticate, requireRole('admin'), handler). Keep claims small, and for sensitive actions check the user's current role in the database, since a token's claims don't change until it expires.

With JWTs, how do you log a user out or block a stolen token?1-6 years

A JWT stays valid until it expires, because the server doesn't look it up, so you design around that. Make the access token short-lived, say 15 minutes, and issue a long-lived refresh token, stored in an HttpOnly, Secure cookie and recorded in the database. When the access token expires, the client calls a refresh endpoint, which checks the refresh token against the database and issues a new pair, rotating the refresh token each time.

Logging out or blocking a user then means deleting their refresh tokens: they lose access within minutes. If an already-used refresh token appears again, treat it as theft and revoke that whole chain of tokens. For instant blocking, keep a small denylist of token ids in Redis.

Reading an answer isn't the same as giving one

In a mock interview you answer new Node.js Developer questions out loud or typed, and every answer is scored, with what to fix and a stronger answer.

Try it free

MongoDB

Queries and aggregation

MongoDB is the most common database alongside Node.js in Indian startups and MERN projects, so interviewers ask you to write real queries and explain indexes and transactions.

In MongoDB, how would you find a customer's 10 most recent orders, returning only a few fields? And how do you update one field?Fresher

db.orders.find({ customerId: id }, { total: 1, status: 1, createdAt: 1 }).sort({ createdAt: -1 }).limit(10). The first argument is the filter, the second a projection choosing which fields come back (_id is included unless you set it to 0), then sort newest first and limit. In Mongoose: Order.find({ customerId: id }).select('total status createdAt').sort('-createdAt').limit(10).

To change one field, use an update operator: db.orders.updateOne({ _id: orderId }, { $set: { status: 'shipped' } }). Other useful operators are $inc for counters, $push to add to an array and $unset to remove a field. A compound index on { customerId: 1, createdAt: -1 } makes the first query fast.

Write an aggregation pipeline that gives total sales per product category for delivered orders.Fresher

db.orders.aggregate([ { $match: { status: 'delivered' } }, { $unwind: '$items' }, { $group: { _id: '$items.category', totalSales: { $sum: { $multiply: ['$items.price', '$items.qty'] } } } }, { $sort: { totalSales: -1 } } ]).

Each stage passes its output to the next. $match filters first, so later stages handle fewer documents and it can use an index. $unwind turns each element of the order's items array into its own document. $group adds up sales by category, and $sort puts the biggest first. Other common stages are $project to shape the output, $lookup to join another collection, and $limit. Keeping $match as early as possible is the main performance tip.

A MongoDB query has become slow as the collection grew. How do you find out why and fix it?1-6 years

Run the query with .explain('executionStats'). If the winning plan shows COLLSCAN, MongoDB is reading every document; compare totalDocsExamined with nReturned. The goal is an IXSCAN with the two numbers close. The database profiler or slow query log shows which queries to look at first.

Then create an index that matches the query. For compound indexes, a useful rule is ESR: equality fields first, then the sort field, then range conditions. For find({ status: 'active', age: { $gt: 30 } }).sort({ createdAt: -1 }), index { status: 1, createdAt: -1, age: 1 }. Don't add indexes blindly: each one slows writes and uses memory. Also look for regex searches without a fixed prefix, which can't use an index well.

When do you need a multi-document transaction in MongoDB, and when can you avoid one?1-6 years

A write to a single document is always atomic in MongoDB, including changes to nested fields and arrays inside it. So if data that changes together lives in one document, like an order with its items embedded, you often don't need a transaction at all.

You need one when several documents must change together or not at all, such as moving money between two wallet documents, or creating an order while reducing stock in the products collection. Transactions need a replica set or a sharded cluster, not a standalone server. In Node, start a session and use session.withTransaction(), passing the session to every operation; it retries on transient errors. Keep transactions short, since long ones hold resources and can be aborted.

Every job

HR and behavioural round

Node.js developers often run the services they build, so the HR round looks for reliability, calm under production pressure and how you work with frontend and product teams.

Tell me about yourself.Fresher

Keep it to about a minute and centre it on backend work you can explain. For example: "I'm a [degree] graduate from [college]. I build backend services in Node.js, mostly REST APIs with Express and MongoDB or PostgreSQL. In [project], I built [the API for a food-ordering app], including JWT authentication, payments through [provider]'s test mode and a job queue for emails, and deployed it on [platform]. I enjoy making APIs fast and reliable, and I'm looking for a Node.js role where I can work on systems with real traffic."

Be ready to draw your project's architecture on a whiteboard if asked.

Why Node.js? Why not Java or Python for the backend?Fresher

Show that you know where Node fits, not that you think it's best for everything. For example: "I chose Node because it's very good at I/O-heavy work like APIs and real-time features, and JavaScript on both frontend and backend lets one team share code and skills. Its package ecosystem is also huge. I know its limits: CPU-heavy work needs worker threads or a separate service, and for some systems Java or Python is the better fit. I'm happy to learn whatever a project needs."

Interviewers like candidates who explain trade-offs calmly instead of defending a favourite.

Are you comfortable being on call for production issues? How would you handle an outage?1-6 years

Be honest about your availability, and show a calm, ordered response. For example: "Yes, I understand on-call comes with owning a service. If I'm paged, I first check how bad it is: dashboards, error rates, and the latest deploy. If a release caused it, I roll back first and investigate afterwards, and I keep the team updated in the incident channel. Once it's stable, I find the root cause and write a short report with actions so it doesn't happen again. At [company or project], [brief real example]."

The point interviewers listen for is restoring service before hunting for the cause.

Tell me about a time you had to learn a new technology quickly.Fresher

Use STAR and show how you learn, not only that you did. For example: "For our final-year project we needed live updates for a quiz, and I'd never used WebSockets (situation and task). I read the Socket.IO documentation, built a tiny chat app in a day to understand rooms and events, then added it to our project and tested it with 50 simulated users (action). The quiz ran smoothly for [number] students, and I now start with any new tool by building a small throwaway prototype (result)."

Pick something relevant to the job, and mention the resources you used.

The frontend developer wants the API to return data in a different shape, and you disagree. What do you do?1-6 years

Show that you focus on what users and the system need, with reasons and a compromise. For example: "A frontend developer wanted one endpoint returning a whole dashboard's data in a single response. I worried it would be slow and hard to reuse. We sat together, looked at what the screen really needed on first load, and agreed on a small summary endpoint for that, with the existing endpoints for the details. The page loaded faster, and the general endpoints stayed clean."

Add that you'd write the agreement into the API contract, such as an OpenAPI spec, so both sides build to the same thing.

Free interview questions, roadmaps and mock interviews: https://myskillgrid.com/interview-questions/nodejs-developer/

Ready to answer them for real?

Your first Node.js Developer mock interview is free: 3 questions, a score and feedback for each, and a stronger answer to learn from, in about 10 minutes.