Full Stack with Node.js Interview Questions for 2 Years Experience
40 questions with answers for candidates with 2 years of experience — 8 easy, 28 medium, 4 hard.
What interviewers expect: 2 years experience
At two years, interviewers expect independent feature ownership. Questions cover API design trade-offs, caching with Redis, transactions, race conditions, performance issues, background jobs and deployment with Docker. Scenario questions about production problems become common. Prepare examples of features you designed end to end, bugs you diagnosed and how you worked with frontend, QA and DevOps teams.
This set is weighted towards Asynchronous JavaScript (10), Authentication and Security (6), Express.js Framework (6), Databases: MongoDB, SQL and Redis (5).
API design trade-offs Redis and queues Concurrency issues Docker and CI basics
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
What is the MongoDB aggregation pipeline? Give an example.
Generate a random 6-digit OTP with crypto.randomInt, store only its hash in Redis with a 5-minute TTL and an attempt counter, and send it through an SMS provider on a DLT-registered template. Verify with a timing-safe compare, limit to about 3 to 5 attempts and rate limit sends per number and IP. On success, issue access and refresh tokens and delete the OTP.
03
How do you scale a Node.js application horizontally?
Run multiple stateless instances behind a load balancer and add more as load grows, using autoscaling on CPU, memory or request rate. Keep state out of the process: sessions in Redis, files in object storage, jobs in a queue. Use Redis adapters for WebSocket broadcasting across instances, and make sure the database can handle the total connection count.
// routes/users.js
const router = require('express').Router();
router.get('/', listUsers);
router.get('/:id', getUser);
router.post('/', auth, createUser);
module.exports = router;
// app.js
app.use(express.json());
app.use('/api/v1/users', require('./routes/users'));
// Keeps each resource in its own module with its own middleware.
05
A partner integration retries POST requests on timeouts and creates duplicate orders in your API. How would you redesign the endpoint?
Require an Idempotency-Key header on order creation and store it with a unique index alongside the created order ID and response. On a repeated key with the same payload, return the original response; with a different payload, return 409 or 422. Also add a unique business constraint such as partner order reference, respond quickly by queueing slow work, and document retry behaviour for partners.
06
Implement the cache-aside pattern with Redis for a product lookup.
Code coverage measures how much code tests execute: lines, branches, functions and statements, reported by Jest with --coverage or c8. It shows untested areas but not whether assertions are meaningful. Chasing 100 percent leads to brittle tests of trivial code. A practical target is around 70 to 85 percent with strong coverage of business logic, error paths and security checks.
Reproduce the load with autocannon or k6, then profile with node --cpu-prof to generate a .cpuprofile file and open it in Chrome DevTools, or use clinic flame to get a flame graph. Wide bars show hot functions. Common culprits are JSON.stringify on huge objects, synchronous crypto, regex backtracking and heavy loops. Fix and re-measure under the same load.
11
Where should a web app store tokens: localStorage or cookies?
localStorage is readable by any JavaScript on the page, so an XSS bug can steal tokens. HttpOnly, Secure, SameSite cookies cannot be read by JavaScript, which is safer for refresh tokens and session IDs. Cookies require CSRF protection, which SameSite=Lax or Strict plus CSRF tokens provide. A common pattern keeps the access token in memory and the refresh token in an HttpOnly cookie.
12
What metrics would you monitor for a Node.js API in production?
Use the RED method for requests: rate, errors and duration with p50, p95 and p99. Add Node-specific metrics: event loop lag, heap used, garbage collection time and active handles, plus CPU, memory and restarts. Monitor dependencies such as database query time, pool usage and cache hit ratio. Export with prom-client or OpenTelemetry and alert on symptoms users feel.
13
A scheduled job processes orders with setInterval every minute, but sometimes runs overlap and duplicate emails are sent. How do you fix it?
setInterval fires regardless of whether the previous async run finished. Use a recursive setTimeout that schedules the next run only after the current one completes, or keep an isRunning flag. If several instances run, use a distributed lock in Redis or a job queue like BullMQ with unique job IDs. Also make email sending idempotent by recording sent message IDs.
PM2 is a process manager for Node.js on virtual machines. It keeps apps running and restarts them on crashes, runs cluster mode across CPU cores, supports zero-downtime reloads, log management and startup scripts. In Docker or Kubernetes you usually do not need PM2, because the orchestrator restarts containers and you scale with replicas, so you run node directly.
15
How can you cancel an in-flight HTTP request in Node.js?
Use AbortController. Create a controller, pass controller.signal to fetch or to libraries that support it such as axios, and call controller.abort() when the request is no longer needed, for example when the client disconnects. AbortSignal.timeout(ms) gives a ready-made timeout signal. Aborting rejects the promise with an AbortError, which you should handle separately from real failures.
16
How do you mock a database or external service in Jest?
Promise.all resolves with all results when every promise fulfils, and rejects as soon as one rejects. Promise.allSettled waits for all and returns each status and value or reason, useful when partial failure is fine. Promise.race settles with the first promise to settle, often used for timeouts. Promise.any resolves with the first fulfilled promise and rejects only if all reject.
OAuth 2.0 lets an app access resources on a user's behalf without seeing their password, for example social login with an existing account. In Authorization Code with PKCE, the app creates a code verifier and sends its hash to the authorisation server. The user logs in and consents, the app receives a code and exchanges it plus the verifier for tokens. PKCE stops intercepted codes being reused.
20
How do you keep yourself updated about security best practices as a backend developer?
I follow the Node.js security release announcements and OWASP cheat sheets, read GitHub security advisories for packages we use and keep Dependabot alerts on. I also practise with labs like OWASP Juice Shop. In my last project I used this to propose helmet, rate limits and stricter input validation, and I shared a short security checklist that the team adopted in pull request reviews.
21
What is the difference between req.params, req.query and req.body?
req.params holds route parameters from the path, such as id in /users/:id. req.query holds query string values like ?page=2&sort=name, used for filtering and pagination. req.body holds the parsed request payload of POST, PUT or PATCH and needs a parser such as express.json(). All are user input and must be validated before use.
Soft delete marks a record as deleted with a flag or deletedAt timestamp instead of removing it, so data can be restored and audited. Trade-offs: every query must filter out deleted rows, unique constraints need care, tables grow over time and privacy laws such as India's DPDP Act may require real deletion of personal data on request. Use partial indexes and periodic purging.
23
Describe a situation where you found and fixed a production performance problem.
Situation: our search API p95 latency jumped from 200 ms to 2 seconds after a release. Task: as on-call engineer I had to find the cause fast. Action: I checked APM traces, saw a new synchronous JSON transform blocking the event loop, moved it to a cached precomputed field and added an event loop lag alert. Result: latency returned to normal within a day and we added a performance check to code review.
24
How do you run integration tests against a real database in CI?
Start a disposable database for the test run, using Docker Compose services in CI, Testcontainers from the test code, or mongodb-memory-server for MongoDB. Run migrations before tests, seed minimal data per test, and clean tables between tests or wrap each test in a rolled-back transaction. Use a separate DATABASE_URL so tests can never touch production data.
25
How do you implement filtering and sorting safely from query parameters?
A hang usually means the middleware never calls next() or sends a response on some path. A 404 often means it was mounted with a wrong path or registered after routes. I check order of app.use calls, add logging at the start and end of the middleware, verify every branch calls next or responds, and check async code passes errors to next.
27
How do you design Express middleware that checks user roles for a route?
Create a factory: const authorize = (...roles) => (req, res, next) => roles.includes(req.user?.role) ? next() : res.status(403).json({ error: 'Forbidden' }). Use it after authentication, for example router.delete('/:id', authenticate, authorize('admin'), handler). Keep roles and permissions in config or the database, and for complex rules move to permission-based checks or a policy library.
28
Compare Prisma, Sequelize and TypeORM for SQL databases in Node.js.
Prisma uses a schema file to generate a fully typed client and has excellent TypeScript support and migrations, though complex raw SQL needs escape hatches. Sequelize is mature, promise-based and widely used in older JavaScript projects. TypeORM uses decorators and entity classes, familiar to Java and NestJS developers. Drizzle is a newer lightweight, SQL-like typed option. Pick based on TypeScript use and team familiarity.
29
Should you use serverless functions like AWS Lambda for a Node.js API?
Serverless suits spiky or low-traffic workloads, event processing and quick prototypes, since you pay per invocation and need no server management. Downsides are cold starts, execution time limits, connection handling with relational databases and harder local debugging. For steady high traffic or long-lived WebSocket connections, containers are usually cheaper and simpler. Many teams mix both.
Nginx acts as a reverse proxy: it terminates TLS, serves static files efficiently, compresses responses, buffers slow clients, load balances across multiple Node instances and adds rate limiting and request size limits. This frees Node to focus on application logic. On cloud platforms a managed load balancer or ingress controller often plays the same role.
31
What is connection pooling and why is it important in Node.js?
Opening a database connection is slow, so a pool keeps a set of open connections that requests borrow and return. pg Pool, mysql2 and the MongoDB driver all pool by default. Size the pool based on database limits and instance count; too small causes waiting, too large overloads the database. In serverless functions, reuse the client across invocations or use a proxy like RDS Proxy.
32
How do you handle async errors in Express 4 routes?
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
router.get('/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new AppError('Not found', 404);
res.json(user);
}));
// Without this, a rejected promise is never sent to the error handler.
// Express 5 does this automatically.
33
What is the difference between var, let and const?
var is function-scoped, hoisted and initialised as undefined, and can be redeclared. let and const are block-scoped and are in a temporal dead zone until declared, so accessing them early throws an error. const prevents reassignment of the binding but not mutation of an object's contents. Modern Node code uses const by default and let when reassignment is needed.
34
How would you handle uncaughtException and unhandledRejection in production?
Log both with full stack traces and context, then exit the process after flushing logs, because state may be corrupted. A process manager like PM2, systemd or Kubernetes restarts it. Never use uncaughtException to keep running as if nothing happened. Since Node 15, unhandled rejections crash the process by default, so every promise chain should have proper error handling.
35
Tell me about a time you had to debug a tricky asynchronous bug.
Situation: customers occasionally received two invoices. Task: I had to find why it happened only in production. Action: I added request IDs to logs and found a forEach with async callbacks plus a retry triggering a second save before the first finished. I replaced it with for...of, added a unique index on invoice number and made the job idempotent. Result: duplicates stopped completely.
Macrotasks are scheduled work like setTimeout, setInterval, setImmediate and I/O callbacks, handled phase by phase by the event loop. Microtasks are promise reactions and queueMicrotask callbacks; they run right after the current synchronous code finishes and before the next macrotask. In Node, process.nextTick callbacks run even before promise microtasks. A long microtask chain can delay timers and I/O.
37
How do you run async tasks in parallel versus in sequence?
// Sequential: each waits for the previous one
for (const id of ids) {
const user = await getUser(id);
results.push(user);
}
// Parallel: start all, then wait together
const users = await Promise.all(ids.map((id) => getUser(id)));
// Use sequential only when steps depend on each other
// or when you must limit load on a downstream service.
38
Write a retry function with exponential backoff for a flaky API call.
OAuth 2.0 is an authorisation framework that issues access tokens for calling APIs; it does not define who the user is. OpenID Connect is an identity layer on top of OAuth 2.0 that adds an ID token, a JWT with user identity claims, and a userinfo endpoint. For social or enterprise single sign-on you use OIDC; for API access delegation you use OAuth scopes.
40
How do you limit concurrency when calling an external API for 10,000 records?
async function runLimited(items, limit, worker) {
const results = [];
let i = 0;
async function next() {
while (i < items.length) {
const idx = i++;
results[idx] = await worker(items[idx]);
}
}
await Promise.all(Array.from({ length: limit }, next));
return results;
}
await runLimited(records, 10, callPartnerApi);
// Or use the p-limit package.
Frequently asked questions
What is asked in Full Stack with Node.js interviews for 2 years of experience?
At two years, interviewers expect independent feature ownership. Questions cover API design trade-offs, caching with Redis, transactions, race conditions, performance issues, background jobs and deployment with Docker. Scenario questions about production problems become common. Prepare examples of features you designed end to end, bugs you diagnosed and how you worked with frontend, QA and DevOps teams.
How many questions should I practise before a Full Stack with Node.js interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 2 years of experience?
Focus first on API design trade-offs, Redis and queues, Concurrency issues, Docker and CI basics.