The MERN stack (MongoDB, Express.js, React, Node.js) is one of the most popular full-stack combinations in the industry, and MERN interviews are uniquely challenging because they test breadth across four distinct technologies plus the integration patterns that connect them. The strongest candidates can trace a user action from a React click through Express middleware, into MongoDB, and back — explaining the trade-offs at each layer. The questions below are organized by technology and progress from fundamentals to production-level scenarios within each section. Each question includes what interviewers are really testing, what separates a textbook answer from a strong production-informed answer, common red flags, and follow-up questions designed to push your understanding deeper. Practice by answering each question out loud before reading the provided answer — the interview is a conversation, not a written exam.Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
1. MongoDB Fundamentals
What is MongoDB and its key features?
What is MongoDB and its key features?
- Schema flexibility — documents in the same collection can have different fields, which is powerful for rapidly evolving products (e.g., an e-commerce catalog where shoes have “size” but laptops have “RAM”)
- Horizontal scalability via sharding — distributes data across machines. MongoDB Atlas can auto-shard, which is how companies like Forbes and Toyota handle billions of documents
- Rich query language — supports filters, projections, regex, geospatial queries, and full-text search natively
- Aggregation framework — a pipeline-based data processing engine that can replace many use cases where you would otherwise need a separate analytics tool
- Replica sets — automatic failover with primary/secondary replication. In production, a 3-node replica set gives you zero-downtime reads even if one node dies
- Multi-document ACID transactions (since v4.0) — often overlooked, but MongoDB does support transactions when you need them
- When would you not choose MongoDB over PostgreSQL for a new project?
- What happens to write performance as your MongoDB collection grows from 1M to 100M documents without proper indexing?
- How does MongoDB’s WiredTiger storage engine handle concurrency at the document level?
Explain BSON vs JSON
Explain BSON vs JSON
| Aspect | JSON | BSON |
|---|---|---|
| Format | Text-based (human-readable) | Binary (machine-optimized) |
| Data types | String, Number, Boolean, Array, Object, null | All JSON types + Date, ObjectId, Binary, Decimal128, Int32, Int64, Regex |
| Traversal | Must parse entire string | Length-prefixed — can skip fields without parsing them |
| Size | Often smaller for simple docs | Slightly larger due to metadata, but faster to decode |
- ObjectId is a 12-byte BSON type that encodes timestamp + machine ID + process ID + counter, giving you globally unique IDs without coordination — this is why MongoDB can generate IDs client-side without hitting the server
- Decimal128 prevents floating-point precision bugs in financial data (e.g.,
0.1 + 0.2 !== 0.3in JSON/JS, but Decimal128 handles it correctly) - Length-prefixed encoding means MongoDB can scan through documents and skip irrelevant fields without deserializing them — critical for query performance on large documents
- If BSON documents are larger than JSON, why does MongoDB still use BSON instead of just compressing JSON?
- How does the ObjectId structure help with sorting by insertion time without a separate
createdAtfield? - What happens when you store a JavaScript
Dateobject vs a string date in MongoDB — how does the query engine treat them differently?
What are collections and documents?
What are collections and documents?
- Capped collections — fixed-size collections that overwrite oldest docs when full. Used for logging (e.g., storing the last 10M log entries with automatic rotation). Created with
db.createCollection("logs", { capped: true, size: 104857600 }) - Schema validation — despite being “schemaless,” production MongoDB almost always uses JSON Schema validation rules:
db.createCollection("users", { validator: { $jsonSchema: { ... } } }). The flexibility is not about having no schema, but about evolving it without downtime migrations - Document size limit — 16MB per document. This sounds large but becomes a real constraint when embedding arrays that grow unboundedly (e.g., embedding all comments inside a blog post document)
- The
_idfield is automatically indexed and must be unique within a collection. If you do not provide it, MongoDB generates an ObjectId
- When would you split data across two collections vs embedding it in one document?
- What happens when a document exceeds the 16MB size limit? How do you redesign to avoid this?
- How do you handle schema evolution in MongoDB when a field needs to change type across millions of existing documents?
Explain CRUD operations
Explain CRUD operations
insertOne()— inserts a single document. ReturnsinsertedIdinsertMany()— inserts an array of documents. By default uses ordered insert (stops on first error). Pass{ ordered: false }for bulk imports where you want to continue past failures — critical for ETL pipelines where you might be inserting 100K docs and a few have duplicate_idvalues
find()— returns a cursor, not an array. This is important: cursors are lazy and stream results, so querying 1M documents does not load them all into memoryfindOne()— returns a single document or null. Internally addslimit(1)to the query- Projections are critical for performance:
find({ status: "active" }, { name: 1, email: 1 })only returns the fields you need, reducing network transfer and memory usage
updateOne()/updateMany()— uses update operators like$set,$inc,$push,$pull. The gotcha: without$set, you will replace the entire documentfindOneAndUpdate()— atomically finds and updates, returning either the old or new document (controlled byreturnDocument: "after"). Essential for race-condition-safe operations like incrementing a counterreplaceOne()— replaces the entire document except_id. Different fromupdateOnewith$set
deleteOne()/deleteMany()— permanent removal- Soft deletes are a common production pattern:
updateOne({ _id }, { $set: { deletedAt: new Date() } })instead of actually deleting, allowing recovery and audit trails
updateOne and replaceOne? Do you understand cursors vs arrays? Do you think about atomicity?Red flag answer: Listing the method names without explaining when to use which, or not knowing that find() returns a cursor.Follow-up:- What is the difference between
updateOnewith$setandreplaceOne? When would you use each? - You need to insert 500K documents as fast as possible. How do you approach this?
- How do you implement an atomic “transfer funds” operation between two user documents?
What is the _id field?
What is the _id field?
_id field is MongoDB’s mandatory primary key — every document must have one, it must be unique within its collection, and it is automatically indexed. If you do not provide it, MongoDB generates a 12-byte ObjectId.ObjectId structure (12 bytes):- Bytes 1-4: Unix timestamp (seconds since epoch)
- Bytes 5-9: Random value (unique per machine/process)
- Bytes 10-12: Incrementing counter
- Sortable by creation time — since the first 4 bytes are a timestamp, sorting by
_idgives you chronological order for free.db.users.find().sort({ _id: 1 })returns documents in insertion order without needing acreatedAtfield - Globally unique without coordination — unlike auto-incrementing SQL IDs, ObjectIds can be generated on the client or across multiple shards without a central authority. This is what makes MongoDB horizontally scalable
- You can extract the timestamp:
ObjectId("507f1f77bcf86cd799439011").getTimestamp()returns the creation date
_id values:
You can use any unique value as _id — strings, numbers, even embedded documents. A common production pattern: using a natural key like { _id: "user_john@example.com" } for collections where you always query by that key, saving an index.What interviewers are really testing: Whether you understand the implications of _id design on sharding, indexing, and query patterns — not just that it exists.Red flag answer: “It’s just an auto-generated unique ID.” This misses the engineering behind ObjectId and the option to customize it.Follow-up:- Why is using an auto-incrementing integer as
_idproblematic in a sharded MongoDB cluster? - You notice that inserts are slow on a sharded collection. How might the
_id(shard key) be causing a hotspot? - Can two documents in different collections have the same
_idvalue? Why or why not?
2. MongoDB Advanced Concepts
What is indexing?
What is indexing?
| Type | Syntax | Use Case |
|---|---|---|
| Single field | { email: 1 } | Queries filtering on one field |
| Compound | { status: 1, createdAt: -1 } | Queries filtering/sorting on multiple fields |
| Multikey | { tags: 1 } (on an array field) | Querying array contents |
| Text | { description: "text" } | Full-text search |
| Geospatial | { location: "2dsphere" } | Location-based queries |
| TTL | { expiresAt: 1 }, { expireAfterSeconds: 0 } | Auto-deleting expired documents (sessions, OTPs) |
| Partial | { email: 1 }, { partialFilterExpression: { status: "active" } } | Index only documents matching a filter — saves space |
{ status: "active", createdAt: { $gte: lastWeek } } sorted by { name: 1 }, the optimal index is { status: 1, name: 1, createdAt: 1 }.Production gotchas:- Each index costs ~8KB of RAM per 1000 documents. A collection with 50M docs and 10 indexes can consume several GB of RAM just for indexes
- Index intersection exists but is often slower than a single compound index — do not rely on MongoDB combining two single-field indexes efficiently
- Use
explain("executionStats")to verify your index is actually being used. TheIXSCANstage means index usage;COLLSCANmeans full scan - Building indexes on large collections blocks writes by default. Use
{ background: true }(deprecated in v4.2+; now indexes are built with an optimized hybrid approach)
explain().Follow-up:- You have a compound index
{ a: 1, b: 1, c: 1 }. Which queries will use this index and which will not? - How would you index a field that contains arrays of objects (e.g.,
orders[].productId)? - Your production database has 20 indexes on a collection but writes are slow. How do you decide which indexes to remove?
Explain aggregation framework
Explain aggregation framework
cat file | grep "error" | sort | uniq -c.Core stages you must know:| Stage | Purpose | Example |
|---|---|---|
$match | Filter documents (like find()) | { $match: { status: "active" } } |
$group | Group by field and aggregate | { $group: { _id: "$city", total: { $sum: "$amount" } } } |
$project | Reshape documents (include/exclude/compute fields) | { $project: { fullName: { $concat: ["$first", " ", "$last"] } } } |
$sort | Sort results | { $sort: { total: -1 } } |
$lookup | Left outer join with another collection | Joins orders with users |
$unwind | Deconstruct an array field into separate documents | Flatten tags: ["a","b"] into two docs |
$facet | Run multiple pipelines in parallel on the same input | Get counts AND top results in one query |
$bucket | Group documents into ranges | Revenue by price brackets |
- Put
$matchfirst — it filters documents early, reducing work for downstream stages, and can use indexes $matchfollowed by$sortcan use a compound index. Once you hit a$groupor$project, indexes are no longer used- For large datasets, use
{ allowDiskUse: true }— aggregation has a 100MB RAM limit per stage by default $lookup(joins) can be expensive. If you are doing$lookupon every query, your schema design might need denormalization
- How would you compute a “running total” or “moving average” using the aggregation framework?
- When would you choose to process data in the application layer (Node.js) vs the aggregation pipeline?
- You have a
$lookupthat joins 10M orders with 1M users. It is slow. What are your optimization options?
What is sharding?
What is sharding?
- Shards — individual replica sets that each hold a portion of the data
- Config servers — store metadata about which data lives on which shard (the “chunk map”)
- mongos router — query router that clients connect to. It reads the config servers to know which shard to send queries to
- A bad shard key creates hotspots — e.g., sharding by
createdAtsends all new writes to the last shard - A good shard key has high cardinality (many unique values), even distribution, and query isolation (most queries target one shard, not all)
- Example: sharding an e-commerce orders collection by
{ userId: "hashed" }distributes writes evenly but means queries like “all orders this week” must scatter-gather across all shards - Example: sharding by
{ region: 1, userId: 1 }enables zone sharding where US data stays on US servers (data residency compliance)
- Once you shard, you cannot change the shard key (pre-v5.0; v5.0+ allows resharding but it is an expensive operation)
- Scatter-gather queries (queries that do not include the shard key) hit all shards and are much slower
- MongoDB recommends sharding when your dataset exceeds what a single replica set can handle (typically 2-5TB or when write throughput exceeds what one primary can handle)
- Chunk splitting and balancing happen automatically — MongoDB moves chunks between shards to maintain even distribution, but this migration consumes I/O
- You chose
{ email: 1 }as your shard key. A year later, your write throughput is bottlenecked. What went wrong and how do you fix it? - Explain the difference between hashed sharding and ranged sharding. When would you choose each?
- How does MongoDB handle a query that does not include the shard key in the filter?
Explain replica sets
Explain replica sets
- The primary writes operations to a capped collection called the oplog (operations log)
- Secondaries continuously tail the primary’s oplog and apply the same operations locally
- This is asynchronous replication by default — secondaries may lag behind the primary (replication lag)
- If the primary becomes unreachable, the remaining members hold an election using the Raft consensus algorithm (since v3.2)
- A secondary with the most up-to-date oplog wins and becomes the new primary
- Failover typically completes in 10-30 seconds — during this window, writes fail
- Applications using the MongoDB driver with
retryWrites: trueautomatically retry failed writes after failover
| Read Preference | Behavior | Trade-off |
|---|---|---|
primary (default) | All reads from primary | Consistent but adds load |
primaryPreferred | Primary unless unavailable | Good default for most apps |
secondary | Reads from secondaries only | May return stale data |
nearest | Reads from lowest-latency member | Best for geo-distributed deployments |
w: 1— acknowledged after primary writes (default)w: "majority"— acknowledged after majority of nodes write. Prevents data loss during failover but adds latency (~2-5ms extra)w: 0— fire-and-forget. Fast but dangerous
- What is replication lag, and how does it affect reads from secondaries in a real-time application?
- You have a 3-node replica set and 2 nodes go down. Can the remaining node accept writes? Why or why not?
- How does write concern
"majority"prevent a specific data loss scenario thatw: 1does not?
What are embedded vs referenced relationships?
What are embedded vs referenced relationships?
_id.- Data is always accessed together (e.g., user + their shipping addresses)
- The embedded array has a bounded, small size (e.g., max 5 addresses)
- You need atomic updates on both parent and child (single-document atomicity)
- The relationship is one-to-few
- Data grows unboundedly (e.g., user + their 50K comments over years)
- Data is accessed independently (e.g., products and reviews have different read patterns)
- The embedded document would exceed 16MB
- The relationship is one-to-many or many-to-many
- Multiple entities reference the same data (normalization avoids duplication)
name and avatarUrl (for display) but references authorId (for the full profile). This avoids a $lookup for the common read path.The “subset pattern”: Embed only the most recent N items (e.g., last 10 reviews) and store the full history in a separate collection.What interviewers are really testing: Data modeling judgment. The correct answer is always “it depends on access patterns,” but you must be able to articulate specific criteria for each choice.Red flag answer: Always embedding or always referencing without considering access patterns, document size limits, or write frequency.Follow-up:- You embedded user comments inside blog posts. Six months later, popular posts have 50K comments and the app is slow. How do you redesign?
- How does the “bucket pattern” help with time-series data in MongoDB?
- When would you use MongoDB’s
$lookupfor referencing, and what is its performance limitation compared to SQL JOINs?
How do MongoDB transactions work, and when should you use them?
How do MongoDB transactions work, and when should you use them?
- Financial operations (transfers, payments) where partial updates are unacceptable
- Multi-collection updates that must be atomic (e.g., creating an order AND reducing inventory)
- Any operation where you need “all or nothing” semantics
- If you can model the operation as a single-document update, you do not need a transaction — single-document operations are already atomic in MongoDB
- Transactions add latency (extra round trips, lock contention) and should not be your default tool
- Most experienced MongoDB developers redesign their schema to avoid needing transactions rather than reaching for them. This is a fundamental difference from SQL-first thinking
- Transactions have a 60-second default timeout and hold locks, so long-running transactions block other operations
- How does MongoDB implement snapshot isolation for transactions, and what are the performance implications?
- If a transaction fails mid-way on a sharded cluster, how does MongoDB ensure consistency across shards?
- Redesign a “transfer funds” operation so it does not require a multi-document transaction.
What is the MongoDB Change Streams feature and how would you use it?
What is the MongoDB Change Streams feature and how would you use it?
- Real-time notifications — notify users when their order status changes
- Cache invalidation — invalidate Redis cache when source data changes
- Event-driven architectures — trigger downstream services when data changes (e.g., send email when user registers)
- Data synchronization — keep Elasticsearch or analytics databases in sync with MongoDB
- Audit logging — record every change for compliance
- Change streams require a replica set (even a single-node replica set for development)
- They support resume tokens — if your listener crashes, you can resume from exactly where you left off without missing events
- You can filter changes with a pipeline:
collection.watch([{ $match: { "fullDocument.status": "shipped" } }]) - Change streams use the oplog, so they add minimal overhead to the database
- How would you handle a scenario where your change stream listener was down for 2 hours? Can you recover all missed events?
- Compare MongoDB Change Streams with using a separate message queue like Kafka. When would you choose each?
- What happens to change streams when a replica set failover occurs?
How do you handle schema migrations in MongoDB?
How do you handle schema migrations in MongoDB?
bulkWrite for performance.schemaVersion field to every document and handle each version in code:migrate-mongo is a popular library that provides a SQL-migration-like workflow for MongoDB.What interviewers are really testing: Whether you understand that schema flexibility is not the same as “no schema management” — and how you handle evolution at scale.Red flag answer: “MongoDB is schemaless so you don’t need migrations.” Every production MongoDB deployment needs a migration strategy.Follow-up:- You need to rename a field used in 50M documents. What is your migration strategy to avoid downtime?
- How do you use MongoDB’s
$jsonSchemavalidator to enforce schema rules while still allowing gradual migration? - What are the risks of lazy migration when running analytics queries that span old and new schema versions?
3. Express.js Basics and Middleware
What is Express.js?
What is Express.js?
http module. It adds three things Node’s raw HTTP server does not have: a routing system, a middleware pipeline, and convenience methods for request/response handling.Why Express exists: Node’s native http.createServer() gives you a raw request and response object. You would have to manually parse URLs, handle different HTTP methods, parse request bodies, set headers, and manage error handling yourself. Express does all of this with a clean API.What Express does NOT do (and why this matters):- No built-in ORM or database layer — you choose (Mongoose, Prisma, Sequelize)
- No built-in authentication — you add Passport, JWT, or custom middleware
- No built-in validation — you add Zod, Joi, or express-validator
- No built-in view engine — you add EJS, Pug, or just send JSON
- When would you choose Fastify over Express for a new project, and why?
- Express has not had a major release in years. What does that mean for its production readiness?
- How does Express compare to Koa in terms of middleware composition?
Explain middleware
Explain middleware
(req, res, next) and can do one of three things: (1) modify req or res, (2) end the request-response cycle, or (3) call next() to pass control to the next middleware.The mental model: Think of middleware as a pipeline of functions that a request flows through. Each function can inspect, modify, or short-circuit the request. This is the Chain of Responsibility pattern.Execution order matters critically:- Application-level (
app.use()) — runs for every request. Use for logging, CORS, body parsing - Router-level (
router.use()) — scoped to a specific router. Use for sub-application middleware like auth on/adminroutes - Error-handling (
(err, req, res, next)) — the 4-parameter signature tells Express this is an error handler. Must be declared last - Third-party —
cors(),helmet(),morgan(). These are just functions that return middleware
next() and next(err), and how the middleware pipeline affects request processing.Red flag answer: Defining middleware without explaining the pipeline concept, execution order, or the importance of calling next().Follow-up:- What happens if a middleware calls both
res.send()ANDnext()? Is that valid? - How does Express know the difference between a regular middleware and an error-handling middleware?
- You have 15 middleware functions. Requests are slow. How do you identify which middleware is the bottleneck?
How does routing work?
How does routing work?
path-to-regexp library to compile route patterns into regular expressions for efficient matching.Route matching rules (order matters):- Routes are matched in the order they are defined — first match wins
- Exact matches take priority over parameterized routes only if defined first
app.use("/api")matches/api,/api/users,/api/anything(prefix matching)app.get("/api")matches only/apiexactly (exact matching)
app.get(), app.post(), app.put(), app.patch(), app.delete(), app.all() (matches any method), app.route() (chain methods for the same path)What interviewers are really testing: Whether you understand route ordering bugs, router composition for modular apps, and the difference between app.use() prefix matching and app.get() exact matching.Red flag answer: Only describing basic app.get("/path", handler) without mentioning route ordering, Router composition, or prefix vs exact matching.Follow-up:- You have
app.get("/users/:id")andapp.get("/users/admin"). A request to/users/adminhits the first route withid = "admin". How do you fix this? - How does Express handle a request that matches no routes?
- What is the performance difference between having 500 routes on
appdirectly vs usingexpress.Router()to organize them?
What are route parameters?
What are route parameters?
req.params): Identify a specific resource.req.query): Filter, sort, or paginate resources./users/42 identifies user 42. /users?role=admin&sort=name filters and sorts the users collection.Validation is critical: req.params.id is always a string, and it can be anything the client sends — including SQL injection attempts, excessively long strings, or special characters. Always validate and sanitize.What interviewers are really testing: API design instincts and security awareness.Red flag answer: Not mentioning that params are always strings, or not considering validation/sanitization.Follow-up:- When would you use
/users/:userId/orders/:orderIdvs/orders/:orderId? What are the trade-offs of nested routes? - How do you validate that
:idis a valid MongoDB ObjectId before it reaches your database query? - What is the difference between
req.params,req.query, andreq.body? When should each be used in REST API design?
How to handle POST requests?
How to handle POST requests?
req.body is undefined unless you add a parser.Essential middleware:extended option matters: extended: true uses the qs library (supports nested objects like user[name]=Ali), while extended: false uses querystring (flat key-value pairs only). For APIs receiving form data with nested structures, use true.Production-grade POST handling:- Set a
limitonexpress.json()to prevent denial-of-service via large payloads (default is 100kb, but 10kb is often sufficient for API requests) - Return
201 Created(not200 OK) for successful resource creation - For file uploads,
express.json()does not work — you needmulterorbusboy - The
Content-Typeheader must match the parser, orreq.bodywill be empty/undefined
app.post("/path", (req, res) => res.json(req.body)) without validation, error handling, or mentioning the body parser middleware.Follow-up:- A client sends a POST with
Content-Type: text/plain. What happens withexpress.json()middleware? How do you handle it? - How do you handle multipart/form-data (file uploads) in Express?
- What is the difference between
express.json()and the deprecatedbody-parserpackage?
What is Express.js and why is it used?
What is Express.js and why is it used?
Example:
Why:
Because Node’s native HTTP module is low-level — you would manually parse URLs, handle routing withif/else chains, parse JSON bodies with Buffer concatenation, and manage error propagation yourself. Express provides clean abstractions for all of this.When:
Use it when building REST APIs, microservices, or server-rendered web apps. For new projects in 2024+, also consider Fastify (2-3x faster due to schema-based serialization) or Hono (edge-first, works on Cloudflare Workers).Where:
Express powers approximately 60% of Node.js web applications in production. Companies like Uber, IBM, and Accenture use it. It is the default choice in most MERN stack tutorials and bootcamps, which contributes to its ecosystem dominance.What interviewers are really testing: Do you just know Express, or do you understand why it exists and what alternatives you would consider?Red flag answer: “Express is a framework for making servers.” No mention of middleware architecture, Node.js HTTP module relationship, or when you would choose alternatives.Follow-up:- If you were starting a new high-performance API from scratch today, would you still choose Express? Why or why not?
- How does Express’s middleware model differ from Koa’s
async/await-based middleware? - Express 5 has been in alpha for years. What problems does it solve that Express 4 does not?
Explain the middleware concept in Express.js
Explain the middleware concept in Express.js
Example:
Why:
Middleware provides a way to modularize logic — logging, authentication, validation — without polluting route handlers. Without middleware, you would duplicate auth checks, logging, and error handling inside every single route handler.When:
Use middleware whenever you want to perform an action before reaching your route (or after response, like error logging). A production Express app typically has 5-15 middleware layers before any route handler executes.Where:
Common production middleware stack (in order):helmet()— security headers (first, because it protects everything below)cors()— CORS handlingmorgan()or custom logger — request loggingexpress.json()— body parsing- Rate limiter — abuse prevention
- Auth middleware — token verification
- Route handlers
- Error handler (always last)
next(), or order significance.Follow-up:- What happens to your app if an early middleware throws an uncaught exception? How does Express handle it?
- Can middleware be async? What is the gotcha with async middleware in Express 4?
- How would you create a middleware that measures the response time of every request and logs it?
What types of middleware exist in Express.js?
What types of middleware exist in Express.js?
| Type | Description | Example |
|---|---|---|
| Application-level | Bound to app | app.use() |
| Router-level | Bound to an Express router | router.use() |
| Built-in | Provided by Express | express.json(), express.static() |
| Third-party | Installed via npm | cors, helmet, morgan |
| Error-handling | 4 parameters (err, req, res, next) | Custom error middleware |
Example:
Why:
To create reusable, layered logic pipelines.When:
Whenever you need pre/post-processing around route handlers.Where:
Throughout the Express lifecycle (before/after routes).What is the role of next() in middleware?
What is the role of next() in middleware?
next() passes control to the next middleware in the stack. If not called, the request hangs (no response sent).Example:
Why:
To chain multiple middlewares for modular logic.When:
When you need layered control (logging → validation → controller).Where:
Always in custom middleware unless it ends the request.What's the difference between app.use() and app.get()?
What's the difference between app.use() and app.get()?
| Function | Purpose |
|---|---|
| app.use() | Registers middleware (runs for all methods unless filtered by path) |
| app.get() | Defines a route handler specifically for GET requests |
Example:
Why:
use() is for pre-processing, get() is for request handling.When:
Useuse() for shared logic like authentication.Where:
Across all routes or specific prefixes.How does Express handle the request-response lifecycle?
How does Express handle the request-response lifecycle?
- Request enters Express
- Passes through middleware stack
- If matched, route handler executes
- If no match → 404 handler
- If error → error-handling middleware
Example Flow:
Why:
Understanding this helps you debug request flow and performance.When:
When analyzing middleware order or debugging missed routes.Where:
In Express core — it’s what powers routing and middleware sequencing.How do you handle errors globally in Express.js?
How do you handle errors globally in Express.js?
Example:
Why:
Centralized error handling avoids repeating try/catch everywhere.When:
In production APIs, always define a global error middleware.Where:
Place at the bottom of middleware stack (after routes).What's the difference between res.send(), res.json(), and res.end()?
What's the difference between res.send(), res.json(), and res.end()?
| Method | Purpose |
|---|---|
| res.send() | Sends response (auto-detects type) |
| res.json() | Sends JSON response (sets content-type) |
| res.end() | Ends response without data |
Example:
Why:
Each method suits different scenarios (raw text vs structured data).When:
Use:send()→ text or HTMLjson()→ APIsend()→ streams or empty responses
Where:
Controllers or route handlers.How can you secure your Express app?
How can you secure your Express app?
- Use Helmet → sets HTTP headers
- Use Rate-limiter → prevent brute-force
- Use CORS properly
- Use
express.json({ limit })→ prevent payload overflows - Disable x-powered-by header
- Validate all inputs (with Joi/Zod)
Example:
Why:
Express is not secure by default; hardening prevents XSS, CSRF, and DoS attacks.When:
Always in production environments.Where:
At app initialization or middleware level.How can you handle async errors in Express route handlers?
How can you handle async errors in Express route handlers?
- Try/catch inside route, or
- Wrap with async error handler.
Example:
Why:
Express doesn’t automatically catch async errors without.catch().When:
Always for async routes (DB calls, APIs).Where:
Use asyncHandler pattern across all async routes.How does Express handle routing internally?
How does Express handle routing internally?
Example:
Why:
To provide performant routing and predictable middleware flow.When:
When debugging routing conflicts or order issues.Where:
Inside express/lib/router/layer.js and route.js source.4. Express.js Authentication & Authorization
What is the difference between authentication and authorization?
What is the difference between authentication and authorization?
How do you implement JWT authentication in Node.js?
How do you implement JWT authentication in Node.js?
Steps:
- User logs in with credentials
- Server verifies credentials and issues a signed JWT using jsonwebtoken
- Client stores JWT (usually in localStorage or cookie)
- Client sends JWT in
Authorization: Bearer <token>header for protected routes
Example:
Why:
JWT avoids database lookups on every request (unlike sessions). The token itself contains the user’s identity and claims, so any server in a load-balanced cluster can verify it independently. This is critical for microservices where there is no shared session store.When:
Use JWT for REST APIs and microservices. However, do not blindly default to JWT — sessions with Redis are often simpler and more secure for monolithic apps. The main advantage of JWT is in distributed, stateless architectures.Where:
Ideal for stateless systems or distributed apps (like mobile + web + microservices all sharing the same auth).Critical security nuances most candidates miss:- Never store JWTs in localStorage — vulnerable to XSS. Use HttpOnly cookies instead
- Always set short expiration (15 minutes for access tokens) and use refresh tokens for longevity
- Include only necessary claims — the payload is Base64-encoded, not encrypted. Anyone can decode it
- Use RS256 (asymmetric) for microservices instead of HS256 (symmetric), so services can verify tokens without knowing the secret
- Validate the
algheader — the “none algorithm” attack changesalgto"none"to bypass signature verification
- A user changes their password. How do you invalidate all their existing JWTs?
- What is the “none algorithm” attack and how do you prevent it?
- Your JWT payload is 8KB. What problems does this cause and how do you reduce it?
When would you prefer sessions over JWT?
When would you prefer sessions over JWT?
- The app is monolithic and server-rendered (like an Express + EJS app)
- You need easy session invalidation (e.g., logout all sessions)
- You store user-specific state in the backend
Example (session-based login):
Why:
Sessions allow you to track user data server-side and revoke tokens instantly.When:
In traditional web apps or dashboards.Where:
Stored in Redis for scalability.How do you protect routes and implement Role-Based Access Control (RBAC)?
How do you protect routes and implement Role-Based Access Control (RBAC)?
What is the difference between RBAC and ABAC?
What is the difference between RBAC and ABAC?
| Concept | RBAC | ABAC |
|---|---|---|
| Full form | Role-Based Access Control | Attribute-Based Access Control |
| Based on | User role | User + Resource attributes |
| Flexibility | Static | Dynamic (context-aware) |
Example (ABAC):
Why:
ABAC allows more granular control.When:
Use ABAC in enterprise apps needing dynamic policies (like file sharing).Where:
Applied at business logic level.How do you securely store passwords in Node.js?
How do you securely store passwords in Node.js?
How would you implement refresh tokens with JWT?
How would you implement refresh tokens with JWT?
How do you handle token revocation (logout) in JWT-based systems?
How do you handle token revocation (logout) in JWT-based systems?
Common strategies:
- Blacklist tokens in Redis
- Rotate refresh tokens (invalidate old ones)
- Use short expiry times for access tokens
Example:
Why:
Prevents reuse of stolen tokens.When:
During logout or detected suspicious activity.Where:
Store in Redis for quick lookup.What are common security vulnerabilities in authentication systems?
What are common security vulnerabilities in authentication systems?
How would you integrate social logins like Google or Facebook?
How would you integrate social logins like Google or Facebook?
5. React Fundamentals & Core Concepts
Beginner Level - React Fundamentals
Beginner Level - React Fundamentals
What is React?
What is React?
- Component-based architecture — UIs are built from self-contained components that manage their own state and compose together. Think of LEGO blocks
- Virtual DOM — React maintains a lightweight in-memory representation of the real DOM. When state changes, React diffs the old and new virtual trees and applies only the minimal necessary changes to the real DOM (reconciliation)
- One-way data flow — data flows down from parent to child via props. Children communicate up via callback props. This makes data flow predictable and debuggable
- Declarative rendering — you describe what the UI should look like for a given state, and React figures out how to update the DOM. You never write
document.getElementById().innerHTML = ...
UI = f(state). This mental model is what makes large applications manageable.What interviewers are really testing: Whether you understand React’s philosophy (declarative, compositional, unidirectional) or just its features.Red flag answer: “React is a framework for building websites.” It is a library (no opinions on routing/state/HTTP), and it builds UIs, not just websites (React Native, React Three Fiber, etc.).Follow-up:- What is the difference between a library and a framework? Why does this distinction matter for React?
- If the Virtual DOM adds overhead (diffing), why does React use it instead of direct DOM manipulation?
- How does React’s one-way data flow compare to Angular’s two-way data binding? What are the trade-offs?
Explain JSX
Explain JSX
React.createElement() calls (or the new JSX transform in React 17+ which does not need React in scope).What JSX compiles to:classbecomesclassName(becauseclassis a reserved word in JS)forbecomeshtmlFor- All tags must be closed (
<img />,<br />) - Style is an object:
style={{ color: "red", fontSize: "16px" }} - Curly braces
{}embed any JavaScript expression:<p>{user.name}</p> - You cannot use
if/elsedirectly in JSX — use ternary or logical AND:{isLoggedIn ? <Dashboard /> : <Login />}
createElement calls and can reason about its limitations.Red flag answer: “JSX is HTML inside JavaScript.” It is not HTML — it is a JS expression that produces React elements. This distinction matters for understanding rendering.Follow-up:- Can you use React without JSX? When would you want to?
- What happens if you return two sibling elements from a component without a wrapper? How do Fragments solve this?
- Why does JSX require a single root element?
What are components?
What are components?
App down to the smallest Button.Functional components (the standard since React 16.8):- Single responsibility — a component should do one thing well. If a component needs 500 lines, split it
- Props are the interface — components receive data and callbacks via props (read-only inputs)
- State is internal — components manage their own dynamic data with
useState/useReducer - Composition over inheritance — React strongly favors composing components together rather than class inheritance. Use
childrenprop, render props, or custom hooks
- Must start with an uppercase letter (
UserCard, notuserCard) — lowercase is interpreted as HTML tags - By convention, one component per file, named the same as the file
- How do you decide when to split a component into smaller components?
- What is the difference between a “presentational” component and a “container” component? Is this pattern still relevant with hooks?
- How do you make a component truly reusable across different projects?
What are props?
What are props?
- Props are immutable in the child — never modify
props.namedirectly - Props can be any JavaScript value: strings, numbers, objects, arrays, functions, even other components
- Default props can be set with destructuring:
function Button({ variant = "primary" }) - Children is a special prop:
<Card><p>Hello</p></Card>— the<p>is available asprops.children
- What happens if you try to mutate a prop inside a child component?
- How do you avoid “prop drilling” when you need to pass data through 5+ levels of components?
- What is the difference between passing a callback via props vs using
useContextfor communication?
What is state?
What is state?
- State is local and encapsulated — no other component can read or modify it directly
- State updates are asynchronous and batched — calling
setStatedoes not immediately change the value - Never mutate state directly —
state.count++will not trigger a re-render. Always use the setter:setCount(count + 1) - For updates based on previous state, use the functional form:
setCount(prev => prev + 1)— this avoids stale closure bugs
- You have 10 pieces of data in a form. Should you use 10 separate
useStatecalls or oneuseStatewith an object? What are the trade-offs? - Why does React not allow direct state mutation? What would happen if it did?
- When should you use
useReducerinstead ofuseState?
What is the difference between render and re-render in React?
What is the difference between render and re-render in React?
Initial Render (Mount Phase):
When a component is first added to the UI:- React calls your component function (App(), Button(), etc.)
- Returns JSX (or React.createElement)
- React builds a virtual DOM tree
- It renders to the real DOM
- Triggers effects like
useEffect(() => {}, [])
Re-render (Update Phase):
A re-render happens when:useStatechange:setCount(1)useReducerdispatch:dispatch({ type: 'ADD' })- Props from parent change
- Context value update
What happens during re-render:
- React re-invokes the component function
- Gets new JSX
- Builds a new virtual DOM
- Diffs it with the previous one (using a diffing algorithm)
- Updates only the changed elements in the real DOM — improving performance
How does useState work under the hood?
How does useState work under the hood?
useState, React:- Creates an internal memory slot (in the Fiber node)
- Stores the initialValue there
- Returns two things:
- The current value from memory
- A setState function that schedules a re-render
React Internally Stores Hooks Like an Array:
hooks = [1, 2]Does setState replace or merge the value in useState?
Does setState replace or merge the value in useState?
Is setState synchronous or asynchronous?
Is setState synchronous or asynchronous?
What are common pitfalls when using useState?
What are common pitfalls when using useState?
| Mistake | Why it breaks |
|---|---|
Calling useState conditionally | Breaks hook ordering |
| Not copying old state in object/array updates | You’ll lose previous data |
| Setting state and expecting it to update instantly | Updates are asynchronous and batched |
Example of state update pitfall:
What are real-world use cases for useState?
What are real-world use cases for useState?
- Form inputs:
useState("") - Toggling modal open/close:
useState(false) - Page tabs:
useState("home") - Simple UI flags (loading, error):
useState(false)
Example:
What is useEffect and when does it run?
What is useEffect and when does it run?
useEffect is a side effect hook that allows your code to run after the component renders.A side effect in React is anything that:- Reaches outside the component (e.g., fetch, timer, localStorage)
- Doesn’t directly involve rendering JSX
Basic Syntax:
Breakdown by Dependency Array:
1. No array = Run on every render:
2. Empty array = Run once (like componentDidMount):
3. With dependencies = Run when values change:
How does useEffect cleanup function work?
How does useEffect cleanup function work?
How does useEffect map to class component lifecycle methods?
How does useEffect map to class component lifecycle methods?
| Lifecycle | useEffect Equivalent |
|---|---|
| componentDidMount | useEffect(() => {...}, []) |
| componentDidUpdate | useEffect(() => {...}, [dep]) |
| componentWillUnmount | Cleanup function inside useEffect |
Example:
Why does useEffect run after the component renders?
Why does useEffect run after the component renders?
useEffect runs after the component renders because:- React first renders the component to the DOM
- Then it runs side effects (like API calls, subscriptions)
- This ensures the UI is visible immediately, and side effects don’t block rendering
useLayoutEffect instead.What happens if you don't provide a key in a list?
What happens if you don't provide a key in a list?
How does state lifting work and why is it needed?
How does state lifting work and why is it needed?
Explain the difference between props drilling and context API. When would you use each?
Explain the difference between props drilling and context API. When would you use each?
Explain the Virtual DOM and how React uses it to optimize rendering
Explain the Virtual DOM and how React uses it to optimize rendering
- React creates a new virtual DOM tree
- It diffs it with the previous one (using a diffing algorithm)
- It updates only the changed elements in the real DOM — improving performance
Code Example:
Why it matters:
Directly updating the DOM is expensive because each change can trigger layout recalculation, repaint, and reflow. The Virtual DOM batches changes and applies them in a single DOM operation.What most candidates get wrong: The Virtual DOM is not faster than manual, targeted DOM manipulation. What it does is make declarative programming fast enough. Without it, you would need to manually track which DOM nodes changed — which is error-prone and scales poorly for complex UIs. The VDOM trades a small performance overhead for a massive improvement in developer experience and code maintainability.The Reconciliation Algorithm (the “diffing”):- React compares elements at the same tree level (O(n) complexity, not O(n^3))
- If the element type changes (
<div>to<span>), React destroys the old subtree and rebuilds - If the type is the same, React only updates the changed attributes
- Keys help React identify which list items changed, were added, or removed
- Svelte compiles away the Virtual DOM entirely. What is the argument for and against React’s VDOM approach?
- How do React’s
keyprops affect the reconciliation algorithm? What happens with unstable keys? - What is the difference between the “render phase” and the “commit phase” in React’s rendering pipeline?
Difference between Controlled and Uncontrolled Components
Difference between Controlled and Uncontrolled Components
- Controlled Component: React controls the input’s value using state
- Uncontrolled Component: The DOM handles the input’s value directly via ref
Code Example (Controlled):
Code Example (Uncontrolled):
What are React Hooks? Can you explain how useEffect works with dependencies?
What are React Hooks? Can you explain how useEffect works with dependencies?
useEffectallows you to perform side effects (like data fetching, DOM updates, event listeners)- The dependency array controls when the effect runs
Code Example:
Behavior based on dependencies:
[]→ runs once (on mount)[var]→ runs when var changes- no array → runs on every render
What are custom hooks and when do you create one?
What are custom hooks and when do you create one?
Intermediate Level - Advanced Hooks & Optimization
Intermediate Level - Advanced Hooks & Optimization
What is automatic batching in React 18?
What is automatic batching in React 18?
Difference from Earlier Versions:
- React 17 and earlier: Only updates in React event handlers were batched
- React 18: All updates are batched automatically, even in async code
Example:
What Happens in React 18:
All 3 updates (setIsLoading(true), setData(), and setIsLoading(false)) are batched together, and the component re-renders only once.What is event handler batching in React?
What is event handler batching in React?
- ✅ This helps avoid multiple re-renders for each individual state update
- ✅ It’s built-in and works in both class and functional components
- ✅ This behavior has existed in React even before version 18
Real-Life Example:
handleSubmit runs, React batches the two state updates, and the component re-renders only once, not twice. This makes the UI faster and avoids flicker or lag.What is flushSync() and when should you use it?
What is flushSync() and when should you use it?
flushSync() is a special React method that tells React to apply state updates immediately—not later, not in the next render cycle, but right now.Normally, React waits and batches updates to make rendering more efficient. But sometimes, you need the UI to update right away—synchronously.Example:
When to Use:
- Testing: When writing tests, you want to ensure all updates are done before checking the result
- Real-time UI: In collaborative editors or games where instant feedback is critical
- Third-party libraries: When working with libraries that depend on real-time DOM updates
How do React.memo, useMemo, and useCallback help optimize re-renders?
How do React.memo, useMemo, and useCallback help optimize re-renders?
- React.memo → skips re-rendering if props haven’t changed
- useMemo → skips recalculating expensive values
- useCallback → skips recreating identical function references
Example with React.memo:
Example with useMemo:
Example with useCallback:
What happens during a re-render with useEffect cleanup?
What happens during a re-render with useEffect cleanup?
useEffect:- React re-executes the component function
- Compares old + new Virtual DOM
- Decides what to update in the real DOM (reconciliation)
- Cleanup function runs (if dependencies changed or component unmounts)
- New effect runs after the updated DOM is committed
Real Example:
On First Render:
countis 0- Renders JSX
- Prints: “Effect runs”
Click Button → Re-render:
setCount(1)triggers re-render- JSX re-evaluated
- Cleanup prints: “Cleanup runs”
- Then new effect prints: “Effect runs”
How do you handle component re-renders and optimize performance?
How do you handle component re-renders and optimize performance?
React.memo()→ prevents re-render if props haven’t changeduseMemo()→ memoizes valuesuseCallback()→ memoizes functions- Avoid anonymous functions inside render if possible
Code Example:
Result:
Child won’t re-render unnecessarily because onClick reference stays stable.Explain the differences between useMemo() and useCallback() in React.
Explain the differences between useMemo() and useCallback() in React.
How would you implement a search feature with debouncing in React?
How would you implement a search feature with debouncing in React?
How would you re-render a component when the window is resized?
How would you re-render a component when the window is resized?
How do you handle asynchronous operations in React using async/await or Promises?
How do you handle asynchronous operations in React using async/await or Promises?
Explain the use case of useEffect() for fetching data from an API.
Explain the use case of useEffect() for fetching data from an API.
useEffect is commonly used for data fetching because it runs after render and can handle async operations.Example:
How do you handle errors in a React app, and what is the role of error boundaries?
How do you handle errors in a React app, and what is the role of error boundaries?
Error Boundary Example:
Important Notes:
- Error boundaries only catch errors in children components
- They don’t catch errors in event handlers, async code, or during SSR
- Use multiple error boundaries for granular error handling
How would you implement dynamic form handling and validation in React?
How would you implement dynamic form handling and validation in React?
Example:
What is lazy loading in React, and how does it improve application performance?
What is lazy loading in React, and how does it improve application performance?
How it works:
- Components are split into separate chunks
- Loaded only when required (route navigation, conditional rendering)
- Reduces initial JavaScript bundle size
Example:
Benefits:
- Faster Initial Load: Smaller initial bundle
- Better Performance: Load code on demand
- Improved User Experience: Faster time to interactive
React.lazy() for manual code splitting in regular React apps.Describe how React Context API can be used for state management in an app.
Describe how React Context API can be used for state management in an app.
What is the role of React Router, and how does it work with dynamic routing?
What is the role of React Router, and how does it work with dynamic routing?
How would you pass data between sibling components in React without using Redux?
How would you pass data between sibling components in React without using Redux?
- Lift State Up: Move shared state to common parent
- Context API: Share data through React Context
- Event Emitters: Use custom event system
- State Management Libraries: Zustand, Jotai (lighter than Redux)
Example - Lifting State:
Example - Context API:
Advanced Level - Expert Questions
Advanced Level - Expert Questions
What are the limitations of React in building large-scale applications?
What are the limitations of React in building large-scale applications?
- State Management Complexity: As apps grow, prop drilling and state management become complex without additional libraries (Redux, Zustand)
- Bundle Size: React itself adds to bundle size, and without code splitting, initial load can be slow
- SEO Challenges: Client-side rendering can hurt SEO without SSR (Next.js helps)
- Performance with Large Lists: Rendering thousands of items can be slow without virtualization
- No Built-in Routing: Requires additional libraries (React Router)
- Learning Curve: JSX, hooks, and patterns require significant learning
- Debugging Complexity: Large component trees can be hard to debug
How does React manage the Virtual DOM, and what are the benefits?
How does React manage the Virtual DOM, and what are the benefits?
- Creating a Virtual Representation: React creates a lightweight JavaScript object representation of the DOM
- Diffing Algorithm: When state changes, React creates a new Virtual DOM tree and compares it with the previous one
- Reconciliation: React determines the minimal set of changes needed
- Batch Updates: React batches DOM updates for efficiency
- Commit Phase: React applies only the necessary changes to the real DOM
Benefits:
- Performance: Avoids expensive direct DOM manipulation
- Efficiency: Only updates what changed, not entire trees
- Predictability: Makes UI updates more predictable and easier to reason about
- Cross-browser Compatibility: Abstracts browser differences
Can React Hooks fully replace Redux for state management? Explain why or why not.
Can React Hooks fully replace Redux for state management? Explain why or why not.
When Hooks are Sufficient:
- Small to medium applications
- Simple state that doesn’t need time-travel debugging
- Local component state or shared state via Context
- No need for middleware (logging, async actions)
When Redux is Better:
- Large applications with complex state
- Need for time-travel debugging
- Middleware requirements (Redux Thunk, Saga)
- Predictable state updates with strict patterns
- DevTools for debugging
Example with useReducer (Redux-like):
What are the best practices for managing state in large React applications?
What are the best practices for managing state in large React applications?
- Lift State Appropriately: Keep state as local as possible, lift only when needed
- Use Context for Global State: For theme, user, locale that many components need
- Consider State Management Libraries: Redux, Zustand, or Jotai for complex state
- Separate Server and Client State: Use React Query or SWR for server state
- Normalize State Shape: Keep state flat and normalized (like Redux)
- Use Custom Hooks: Abstract state logic into reusable hooks
- Avoid Prop Drilling: Use Context or state management for deeply nested props
Example Structure:
How would you optimize performance in a React app with large component trees?
How would you optimize performance in a React app with large component trees?
- Code Splitting: Use
React.lazy()andSuspenseto split code - Memoization: Use
React.memo(),useMemo(),useCallback() - Virtualization: Use
react-windoworreact-virtualizedfor long lists - Avoid Inline Functions: Move functions outside render or use
useCallback - Proper Keys: Use stable, unique keys in lists
- State Colocation: Keep state close to where it’s used
- React.memo with Custom Comparison: For expensive components
Example:
Explain React's Strict Mode and its impact on development.
Explain React's Strict Mode and its impact on development.
What it does:
- Double Invokes: Intentionally double-invokes functions to detect side effects
- Deprecation Warnings: Warns about deprecated APIs
- Unsafe Lifecycle Warnings: Identifies unsafe lifecycle methods
- Legacy String Ref Warnings: Warns about string refs
Example:
Impact:
- Helps catch bugs early in development
- Ensures components are resilient to re-mounting
- Prepares for future React features
- Note: Strict Mode only runs in development, not production
How can you prevent unnecessary re-renders in React functional components?
How can you prevent unnecessary re-renders in React functional components?
- React.memo(): Wrap components to prevent re-renders if props haven’t changed
- useMemo(): Memoize expensive calculations
- useCallback(): Memoize function references
- State Colocation: Move state down to the component that needs it
- Split Components: Break large components into smaller ones
- Avoid Creating Objects in Render: Move object/array creation outside render
Example:
Describe the key differences between functional and class components in React.
Describe the key differences between functional and class components in React.
| Aspect | Functional Components | Class Components |
|---|---|---|
| Syntax | Function declaration | Class extends Component |
| State | useState hook | this.state |
| Lifecycle | useEffect hook | componentDidMount, etc. |
| Performance | Generally faster | Slightly more overhead |
| Code Size | Less code | More boilerplate |
| Hooks | ✅ Can use all hooks | ❌ Cannot use hooks |
this binding | Not needed | Required for methods |
Example Comparison:
What is the significance of the React Fiber architecture?
What is the significance of the React Fiber architecture?
- Work units (Fibers): Each component instance becomes a “fiber” — a JavaScript object that represents a unit of work. React can process these units one at a time, yielding to the browser between them
- Interruptible rendering: React can pause rendering mid-tree, handle a high-priority update (like a user click), and then resume where it left off. The old reconciler could not do this
- Priority-based scheduling: User interactions (typing, clicking) get higher priority than background updates (data fetching, analytics). This is what powers
useTransitionanduseDeferredValuein React 18
type (component function/class), key, child (first child fiber), sibling (next sibling fiber), return (parent fiber), pendingProps, memoizedState, effectTag (what DOM operation to perform).Concrete features Fiber enables:- Concurrent Mode / Concurrent Features (React 18) — rendering can be interrupted
- Suspense — pause rendering while waiting for async data
- Automatic batching — group multiple state updates into one render
- Transitions (
useTransition) — mark non-urgent updates so they do not block user input - Error boundaries — handle errors at the fiber level without crashing the entire tree
- Render phase (interruptible): React walks the fiber tree, computing what changed. No side effects. Can be paused/restarted
- Commit phase (synchronous): React applies all changes to the DOM in one batch. Cannot be interrupted
- How does
useTransitionuse Fiber’s priority system to keep input fields responsive during expensive state updates? - What is the difference between the “render phase” and “commit phase” in terms of what can and cannot be interrupted?
- Why does React warn against side effects during render? How does Fiber’s interruptible rendering make side effects in render especially dangerous?
How does React handle side effects, and how can you manage them effectively?
How does React handle side effects, and how can you manage them effectively?
useEffect hook, which runs after render.Effective Management:
- Separate Concerns: Use multiple
useEffecthooks for different side effects - Proper Dependencies: Always include all dependencies in the dependency array
- Cleanup: Always clean up subscriptions, timers, and event listeners
- Avoid Infinite Loops: Be careful with dependencies that change on every render
Example:
How would you implement custom hooks to abstract logic in React?
How would you implement custom hooks to abstract logic in React?
Explain useRef and when you would use it instead of useState
Explain useRef and when you would use it instead of useState
useRef creates a mutable reference that persists across re-renders but, crucially, does not trigger a re-render when changed. This makes it fundamentally different from useState.Three primary use cases:1. Accessing DOM elements directly:| Aspect | useState | useRef |
|---|---|---|
| Triggers re-render on change | Yes | No |
| Persists across renders | Yes | Yes |
| Mutable directly | No (use setter) | Yes (ref.current = x) |
| Best for | UI state (what user sees) | Internal state (timer IDs, DOM refs, previous values) |
useState for timer IDs or render counts causes unnecessary re-renders.Red flag answer: “useRef is for accessing DOM elements.” That is one use case, but missing the mutable-value-without-re-render use case shows shallow understanding.Follow-up:- Why does storing a timer ID in
useStatevsuseRefcause different behavior? Which is correct? - How does
useRefavoid the stale closure problem thatuseStatecan have in callbacks? - What is
useImperativeHandleand when would you pair it withforwardRef?
What are Higher-Order Components (HOCs) and when would you use them?
What are Higher-Order Components (HOCs) and when would you use them?
EnhancedComponent = higherOrderComponent(WrappedComponent).The key insight: HOCs do not modify the input component. They wrap it, creating a new component that renders the original with additional props or behavior.Example:
useAuth() hook is simpler than withAuth(Component). However, HOCs are still valid for:- Cross-cutting concerns that need to wrap rendering (error boundaries, layout wrappers)
- Third-party library integration (React-Redux’s
connect()is a HOC) - When you need to intercept or modify rendering, not just add data
- HOCs lose the original component’s static methods — use
hoist-non-react-staticsto copy them - Refs do not pass through — use
React.forwardRefto forward refs - Too many HOCs create “wrapper hell”:
withAuth(withTheme(withRouter(Component)))— this is why hooks won
- How would you refactor a HOC into a custom hook? What changes and what does not?
- What is the “wrapper hell” problem with HOCs and how do hooks solve it?
- Can you compose multiple HOCs? What problems arise from deep HOC nesting?
What is the Render Props pattern?
What is the Render Props pattern?
Example:
| Era | Pattern | Pros | Cons |
|---|---|---|---|
| 2015 | Mixins | Simple | Naming conflicts, tight coupling |
| 2016 | HOCs | Composable | Wrapper hell, prop conflicts |
| 2017 | Render Props | Explicit data flow | Callback nesting, less readable |
| 2019+ | Custom Hooks | Clean, composable, no wrappers | Requires hooks rules knowledge |
<Field> component).What interviewers are really testing: Pattern evolution knowledge. They want to see you can trace the progression from mixins to hooks and explain why each pattern emerged.Red flag answer: “Render props are for passing functions as props.” Technically true but misses the purpose: decoupling logic from presentation.Follow-up:- How would you convert a render props component into a custom hook?
- What is the performance concern with inline render prop functions, and how do you mitigate it?
- The
childrenprop can also be used as a render prop (<Mouse>{pos => ...}</Mouse>). When would you usechildrenvs a named render prop?
What are the benefits of server-side rendering (SSR) in React applications?
What are the benefits of server-side rendering (SSR) in React applications?
- SEO: Search engines can crawl fully rendered HTML
- Faster Initial Load: Users see content immediately
- Better Performance: Reduces client-side JavaScript execution
- Social Sharing: Proper meta tags for social media previews
- Accessibility: Works better with screen readers on initial load
SSR vs CSR:
| Aspect | SSR | CSR |
|---|---|---|
| Initial HTML | Fully rendered | Empty shell |
| SEO | ✅ Excellent | ❌ Poor |
| Time to First Byte | Slower | Faster |
| Time to Interactive | Faster | Slower |
getServerSideProps and Server Components.How do you handle styling in React components? Discuss different approaches.
How do you handle styling in React components? Discuss different approaches.
- CSS Modules: Scoped CSS files
- Styled Components: CSS-in-JS library
- Tailwind CSS: Utility-first CSS framework
- Inline Styles: JavaScript objects
- CSS-in-JS: Libraries like Emotion, styled-components
Example - CSS Modules:
Example - Styled Components:
Example - Tailwind:
How would you optimize React app performance when handling large lists or grids?
How would you optimize React app performance when handling large lists or grids?
- Virtualization: Only render visible items
- Pagination: Load items in chunks
- Memoization: Memoize list items
- Proper Keys: Use stable, unique keys
Example with react-window:
Example with Pagination:
Explain the difference between shallow and deep comparison in React's shouldComponentUpdate.
Explain the difference between shallow and deep comparison in React's shouldComponentUpdate.
How do you handle asynchronous code execution and state updates in React?
How do you handle asynchronous code execution and state updates in React?
How would you structure large React or Next.js projects?
How would you structure large React or Next.js projects?
- Feature-based organization helps scale
- Shared state or UI logic goes to
/contextor/hooks - Pages are lazy-loaded in Next.js automatically
What are error boundaries and how do they work?
What are error boundaries and how do they work?
Example:
Explain the concept of render optimization in React
Explain the concept of render optimization in React
- Use
React.memo()for pure functional components - Use
useCallbackanduseMemoto memoize functions and computed values - Avoid creating new objects/functions in render unnecessarily
Example:
What is the difference between a monolithic and modular frontend architecture?
What is the difference between a monolithic and modular frontend architecture?
| Aspect | Monolithic | Modular (Micro-frontend) |
|---|---|---|
| Structure | All components tightly coupled and deployed together | App divided into independent modules (built & deployed separately) |
| Team Size | Better for small teams | Ideal for large teams, multiple domains |
| Deployment | Single deployment | Independent deployments |
| Tech Stack | Single technology | Tech diversity (React + Vue + Angular) |
When to use Modular:
- Large teams, multiple domains
- Independent deployments
- Tech diversity requirements
How do you manage global state in large apps?
How do you manage global state in large apps?
- Context API for light global state
- Redux Toolkit / Zustand / Jotai for complex or shared logic
- React Query for server state
Example with Zustand:
Explain React's reconciliation process and how it updates the DOM efficiently.
Explain React's reconciliation process and how it updates the DOM efficiently.
Process:
- Render Phase: React creates a new Virtual DOM tree
- Diffing: Compares new tree with previous tree
- Reconciliation: Determines minimal set of changes
- Commit Phase: Applies changes to real DOM
Key Optimizations:
- Same-level Comparison: React compares elements at the same level
- Key Optimization: Keys help React identify which items changed
- Batching: Multiple updates are batched together
- Fiber Architecture: Enables incremental rendering
Example:
6. NEXT JS
Explain the difference between the Pages Router and App Router in Next.js
Explain the difference between the Pages Router and App Router in Next.js
| Feature | Pages Router (pages/) | App Router (app/) |
|---|---|---|
| Introduced | Before Next.js 13 | Next.js 13+ |
| Rendering | CSR, SSR, SSG | RSC, SSR, SSG, ISR |
| File-based routing | Yes | Yes (with nested layouts) |
| Data fetching | getServerSideProps, getStaticProps | Async components or fetch directly |
| Layouts | Custom | Built-in persistent layouts |
| Server Components | ❌ | ✅ Default |
| Client Components | ✅ | ✅ (with “use client”) |
Example (App Router):
Example (Pages Router):
What are Server Components and Client Components in Next.js 13+?
What are Server Components and Client Components in Next.js 13+?
-
Server Components (default):
- Run on the server (never in browser)
- Can fetch data directly
- Reduce bundle size and improve performance
-
Client Components:
- Run in the browser
- Must be marked with “use client”
- Can use useState, useEffect, event handlers, etc.
Example:
How does SSR differ from SSG and ISR in Next.js?
How does SSR differ from SSG and ISR in Next.js?
| Mode | Description | When to Use |
|---|---|---|
| SSR (Server-Side Rendering) | Page rendered at every request | Dynamic data that changes often (e.g., dashboard) |
| SSG (Static Site Generation) | Page pre-rendered at build time | Static content (e.g., blog, docs) |
| ISR (Incremental Static Regeneration) | SSG + revalidation after a time interval | Semi-static content (e.g., news feed) |
Example (ISR):
When would you choose SSR vs CSR?
When would you choose SSR vs CSR?
- SSR: When SEO and fast first paint are important
- CSR (Client-Side Rendering): When user-specific or highly interactive data is needed (like dashboards)
Example CSR (client fetch):
How do you handle API routes in Next.js?
How do you handle API routes in Next.js?
Explain middleware and where it runs
Explain middleware and where it runs
- Redirect or rewrite requests
- Check authentication
- Modify headers
Example:
How does generateMetadata work in the App Router?
How does generateMetadata work in the App Router?
generateMetadata() allows dynamic SEO metadata generation per page.Example:
What are Layouts and Parallel Routes in Next.js?
What are Layouts and Parallel Routes in Next.js?
How does Next.js handle Image Optimization?
How does Next.js handle Image Optimization?
<Image> component optimizes images automatically:- Lazy loading
- Responsive resizing
- WebP conversion
Example:
How do you do Internationalization (i18n) in Next.js?
How do you do Internationalization (i18n) in Next.js?
What are Server Actions in Next.js and how do they change full-stack development?
What are Server Actions in Next.js and how do they change full-stack development?
"use server" directive and represent a fundamental shift in how full-stack React apps handle mutations.Example — form submission without an API route:
- Next.js creates an API endpoint automatically for each Server Action
- The client sends a POST request with the form data
- The server executes the function and can revalidate cached pages
- Progressive enhancement: forms work even without JavaScript enabled (the action attribute is a real URL)
| Use Case | Server Actions | API Routes |
|---|---|---|
| Form submissions | Preferred | Works but more boilerplate |
| Database mutations | Preferred | Works |
| Third-party API calls from client | Not ideal | Preferred |
| Webhook endpoints | Cannot use | Required |
| Public API for mobile apps | Cannot use | Required |
- Server Actions are effectively public API endpoints — always validate input and authenticate the user
- Never trust form data; use Zod or similar validation
- Use
cookies()orheaders()to verify authentication inside the action
- How do Server Actions handle optimistic updates in the UI? How does
useOptimisticwork with them? - What happens if a Server Action throws an error? How does Next.js handle it on the client?
- Are Server Actions safe from CSRF attacks? How does Next.js protect them?
Explain Next.js caching layers and when to use each
Explain Next.js caching layers and when to use each
| Cache | What it caches | Where | Duration | Opt-out |
|---|---|---|---|---|
| Request Memoization | Duplicate fetch() calls in the same render | Server | Single render pass | Cannot opt out |
| Data Cache | fetch() responses | Server | Persistent (until revalidation) | cache: "no-store" |
| Full Route Cache | Rendered HTML and RSC payload | Server | Persistent (until revalidation) | Dynamic functions or cache: "no-store" |
| Router Cache | RSC payload for visited routes | Client | Session-based (30s for dynamic, 5min for static) | router.refresh() |
revalidatePath("/path") or revalidateTag("tag") in your Server Action.What interviewers are really testing: Whether you have actually built Next.js apps in production and debugged caching issues, or just know the theory.Red flag answer: “Next.js caches pages for performance.” This is too vague — not specifying which cache layer or how to invalidate reveals no hands-on experience.Follow-up:- You updated a database record via a Server Action, but the page still shows old data. Walk through every caching layer and how you would debug this.
- What is the difference between
revalidatePathandrevalidateTag? When would you use each? - How does the Router Cache interact with browser back/forward navigation?
7. React State Management and Advanced Patterns
What is code splitting?
What is code splitting?
React.lazy without discussing when and where to split, or not mentioning Suspense fallbacks.Follow-up:- How do you analyze your bundle to decide where to split? What tools do you use?
- What happens if a lazy-loaded chunk fails to load (network error)? How do you handle it gracefully?
- What is the difference between code splitting and tree shaking?
Explain error boundaries
Explain error boundaries
try/catch for the component tree.Critical details:- Error boundaries must be class components — there is no hook equivalent (as of React 19). The
react-error-boundarylibrary provides a functional wrapper - They catch errors during rendering, lifecycle methods, and constructors of the tree below them
- They do NOT catch: event handler errors, async errors (setTimeout, Promises), server-side rendering errors, or errors in the error boundary itself
- How do you log errors caught by error boundaries to a monitoring service like Sentry?
- Why can error boundaries only be class components? Will this change?
- How do you implement a “retry” button in an error boundary to attempt re-rendering the failed component?
What are portals?
What are portals?
overflow: hidden or z-index stacking context issues). Without portals, a modal inside a overflow: hidden container would be clipped.The event bubbling gotcha: Even though the modal renders in #modal-root in the DOM, a click inside it will still trigger onClick handlers on its React parent. This is because React’s synthetic event system follows the React tree, not the DOM tree. This is useful (parent can handle modal events) but can be surprising.What interviewers are really testing: Whether you understand the DOM vs React tree distinction and can explain why portals maintain React event bubbling.Red flag answer: “Portals render elements outside the parent div.” This misses the key insight about event propagation.Follow-up:- How does event bubbling work with portals? If a portal renders into
document.body, does clicking inside it bubble to the React parent? - How would you manage focus trapping inside a portal-based modal for accessibility?
- When would you use a portal vs just using CSS
position: fixedand highz-index?
What is React Router?
What is React Router?
pushState, replaceState, popstate event) to update the URL and trigger component re-renders. The URL becomes the “state” that determines which component tree to render.Key concepts in React Router v6+:<BrowserRouter>— wraps the app and provides routing context<Routes>/<Route>— defines the route-to-component mapping<Link>/<NavLink>— navigates without page reloaduseNavigate()— programmatic navigationuseParams()— access route parametersuseSearchParams()— access and modify query strings<Outlet>— renders child routes in nested layouts
<Route path="/" element={<Home />} /> without discussing nested routes, protected routes, or lazy loading route components.Follow-up:- How do you implement protected routes that redirect unauthenticated users to login?
- What is the difference between
BrowserRouterandHashRouter? When would you use each? - How does React Router interact with
React.lazyfor route-based code splitting?
Explain performance optimization
Explain performance optimization
-
Architecture-level (highest impact):
- Keep state as local as possible — state in a top-level provider re-renders everything below it
- Separate server state (React Query/SWR) from client state (Zustand/useState)
- Use code splitting to reduce initial bundle size
-
Component-level:
React.memo()— skip re-rendering when props have not changed. Only useful when re-rendering is actually expensive- Split large components — a component with both expensive and cheap parts should be split so the expensive part can be memoized independently
- Use proper
keyprops in lists — wrong keys cause unnecessary DOM destruction/recreation
-
Computation-level:
useMemo()— cache expensive calculations. Do not use for cheap operations (the memoization overhead is not free)useCallback()— stabilize function references passed to memoized children
-
Rendering-level:
- Virtualization (
react-window) for lists with 100+ items <Suspense>for streaming and lazy loadinguseTransition/useDeferredValue(React 18) for non-urgent updates
- Virtualization (
useMemo on every value. Over-memoization is a code smell.Red flag answer: “Use React.memo, useMemo, and useCallback everywhere.” This shows no understanding of when optimization helps vs when it adds unnecessary complexity.Follow-up:- When does
React.memoactually hurt performance instead of helping? - How do you use the React DevTools Profiler to find components that re-render too often?
- Your React app takes 4 seconds to become interactive on mobile. Walk me through your optimization strategy.
What's the difference between Redux, Zustand, and React Query?
What's the difference between Redux, Zustand, and React Query?
| Library | Purpose | Best For |
|---|---|---|
| Redux | Centralized predictable state management (global state) | Large apps needing structured data flow |
| Zustand | Lightweight state management using hooks | Simpler state sharing without boilerplate |
| React Query (TanStack Query) | Server-state management (fetching, caching, syncing) | Handling API data efficiently |
Example: Redux
Example: Zustand
Example: React Query
Summary:
- Redux → predictable, structured, global state
- Zustand → minimal, fast local/global store
- React Query → asynchronous data fetching & caching
How do you handle server and client data synchronization?
How do you handle server and client data synchronization?
- Use React Query or SWR for automatic refetching
- Use mutations and invalidate queries after updates
Example with React Query:
invalidateQueries ensures fresh data after mutation — syncing server + client.Explain how to use useReducer hook
Explain how to use useReducer hook
How would you implement pagination or infinite scrolling efficiently?
How would you implement pagination or infinite scrolling efficiently?
- Use API parameters like
?page=1&limit=10 - Use React Query with
getNextPageParamfor infinite scroll
Example:
What is hydration and what problems can occur during hydration in Next.js?
What is hydration and what problems can occur during hydration in Next.js?
- Hydration = The process where React on the client attaches event handlers to the already rendered HTML from the server
- Hydration mismatch happens when server-rendered HTML differs from client render output
Example (problematic):
Fix:
Common causes of mismatch:
- Conditional rendering differences between server/client
- Using
window,localStorageon the server - Non-deterministic values during SSR
How do you handle loading and error states in data fetching?
How do you handle loading and error states in data fetching?
How can you persist state across pages or reloads?
How can you persist state across pages or reloads?
How do you prefetch data in Next.js App Router?
How do you prefetch data in Next.js App Router?
What are Higher-Order Components (HOCs) and when would you use them?
What are Higher-Order Components (HOCs) and when would you use them?
Example:
What is the Render Props pattern?
What is the Render Props pattern?
Example:
What is Compound Components pattern?
What is Compound Components pattern?
Example:
What is the Provider Pattern?
What is the Provider Pattern?
Example:
What is code-splitting and how do you implement it in React?
What is code-splitting and how do you implement it in React?
React.lazy() and Suspense for this.Example:
How do you structure large React or Next.js projects?
How do you structure large React or Next.js projects?
- Feature-based organization helps scale
- Shared state or UI logic goes to
/contextor/hooks - Pages are lazy-loaded in Next.js automatically
What are error boundaries and how do they work?
What are error boundaries and how do they work?
Example:
Explain the concept of render optimization in React
Explain the concept of render optimization in React
- Use
React.memo()for pure functional components - Use
useCallbackanduseMemoto memoize functions and computed values - Avoid creating new objects/functions in render unnecessarily
Example:
What is the difference between a monolithic and modular frontend architecture?
What is the difference between a monolithic and modular frontend architecture?
| Aspect | Monolithic | Modular (Micro-frontend) |
|---|---|---|
| Structure | All components tightly coupled and deployed together | App divided into independent modules (built & deployed separately) |
| Team Size | Better for small teams | Ideal for large teams, multiple domains |
| Deployment | Single deployment | Independent deployments |
| Tech Stack | Single technology | Tech diversity (React + Vue + Angular) |
When to use Modular:
- Large teams, multiple domains
- Independent deployments
- Tech diversity requirements
How do you manage global state in large apps?
How do you manage global state in large apps?
- Context API for light global state
- Redux Toolkit / Zustand / Jotai for complex or shared logic
- React Query for server state
Example with Zustand:
8. React and Next JS testing
Why is testing important in React apps?
Why is testing important in React apps?
Types of tests:
- Unit tests → Test small pieces (functions, components)
- Integration tests → Verify component interaction
- E2E tests → Test entire user flows (via Playwright / Cypress)
What testing libraries are commonly used in React and Next.js?
What testing libraries are commonly used in React and Next.js?
- Jest → Testing framework (built-in with Next.js)
- React Testing Library (RTL) → DOM interaction & behavior testing
- Playwright / Cypress → End-to-end browser testing
How do you test a simple React component using Jest and RTL?
How do you test a simple React component using Jest and RTL?
How do you mock API calls in tests?
How do you mock API calls in tests?
How do you test Next.js pages or server functions?
How do you test Next.js pages or server functions?
How do you perform snapshot testing in React?
How do you perform snapshot testing in React?
Example:
How do you test components that use React Context?
How do you test components that use React Context?
How do you debug React apps efficiently?
How do you debug React apps efficiently?
- Use React Developer Tools in browser
- Add
console.log()inside hooks or effects - Use VSCode breakpoints
- Use why-did-you-render library to detect unnecessary re-renders
- For Next.js, run
next dev --inspectfor Node debugging
How do you handle test coverage?
How do you handle test coverage?
Key metrics to aim for:
- 80%+ line coverage
- 70%+ branch coverage
What are common issues you debug in Next.js?
What are common issues you debug in Next.js?
| Problem | Possible Cause | Solution |
|---|---|---|
| Hydration Error | Mismatch between SSR and client rendering | Avoid using browser-only APIs during SSR |
| API Route not working | Wrong file structure or method name | Ensure correct /app/api/.../route.js naming |
| 404 after deploy | Static export paths missing | Check dynamic routes and revalidate configs |
| Performance lag | Over-fetching or large bundle | Use lazy loading, memoization, code-splitting |
9. Performance and Optimization React | Next JS
How do you prevent unnecessary re-renders in React?
How do you prevent unnecessary re-renders in React?
- Wrap pure components with
React.memo - Use
useCallbackto memoize event handlers - Use
useMemofor computed values - Split large components
- Keep state as local as possible
Example:
How do you optimize images and assets in Next.js?
How do you optimize images and assets in Next.js?
How do you handle large bundle sizes?
How do you handle large bundle sizes?
- Use dynamic imports:
- Analyze with:
- Move heavy logic to server components (Next.js App Router)
How does React Suspense help performance?
How does React Suspense help performance?
What are server and client components in Next.js 13+?
What are server and client components in Next.js 13+?
- Server Components: Run on the server; ideal for data fetching and heavy logic
- Client Components: Run in the browser; used for interactivity
Example:
How do you cache API responses in Next.js?
How do you cache API responses in Next.js?
- Use
revalidatefor ISR (Incremental Static Regeneration) - Use fetch caching options (
force-cache,no-store,revalidate)
Example:
What are some performance debugging tools?
What are some performance debugging tools?
- React Profiler → Measures render time
- Lighthouse → Measures Core Web Vitals
- Next.js build analyzer → Bundle size breakdown
- Chrome DevTools Performance tab → JS execution time
What are React rendering phases and how to profile them?
What are React rendering phases and how to profile them?
- Render phase → Reconciliation (diffing virtual DOM)
- Commit phase → Apply changes to real DOM
How do you handle performance in lists (1000+ items)?
How do you handle performance in lists (1000+ items)?
What are best practices for Next.js SEO performance?
What are best practices for Next.js SEO performance?
- Use
generateMetadata()in App Router - Lazy load below-the-fold components
- Preload fonts via
next/font - Optimize routes with static rendering when possible
- Use semantic HTML and proper heading structure
- Implement structured data with JSON-LD
- Ensure fast loading times with image optimization
10. Security and Best Practices in React | Next JS
How do you prevent XSS (Cross-Site Scripting) in React?
How do you prevent XSS (Cross-Site Scripting) in React?
dangerouslySetInnerHTML.Safe Practice:
Never insert user input into HTML directly.How do you secure API routes in Next.js?
How do you secure API routes in Next.js?
- Use authentication middleware
- Validate incoming data with Zod or Yup
- Restrict methods (GET, POST, etc.)
- Avoid exposing sensitive env vars to the client
Example:
How do you store secrets securely in Next.js?
How do you store secrets securely in Next.js?
- Use
.env.localfor local secrets - Access via
process.env.SECRET_KEYon server - Never expose private keys using
NEXT_PUBLIC_prefix unless intentional
How do you prevent CSRF attacks?
How do you prevent CSRF attacks?
- Use tokens or double-submit cookies
- For sensitive actions, validate Origin and Referer headers
- Use libraries like NextAuth.js (handles CSRF internally)
- Implement SameSite cookies
Example cookie settings:
How do you handle authentication securely?
How do you handle authentication securely?
- Use HTTP-only cookies for tokens
- Avoid storing JWT in localStorage (can be stolen via XSS)
- Implement proper session management
- Use secure and sameSite flags for cookies
Secure cookie configuration:
What are CSP (Content Security Policies)?
What are CSP (Content Security Policies)?
Example:
How do you handle rate limiting for APIs?
How do you handle rate limiting for APIs?
How do you prevent sensitive data exposure in logs?
How do you prevent sensitive data exposure in logs?
How to handle CORS safely?
How to handle CORS safely?
- Always whitelist origins
- Avoid using
*in production - Use middleware to configure headers
Example:
Access-Control-Allow-Origin: * in production for sensitive endpoints.How to secure Next.js deployment?
How to secure Next.js deployment?
Security Checklist:
- ✅ Use HTTPS
- ✅ Set
NODE_ENV=production - ✅ Use environment variables, not hardcoded secrets
- ✅ Keep dependencies updated (
npm audit) - ✅ Use a WAF (Web Application Firewall) if available
- ✅ Implement proper CORS policies
- ✅ Use security headers (CSP, HSTS, X-Frame-Options)
- ✅ Regular security scanning and penetration testing
- ✅ Monitor for suspicious activities
- ✅ Implement proper error handling (don’t leak stack traces)
How do you protect sensitive environment variables in Next.js?
How do you protect sensitive environment variables in Next.js?
NEXT_PUBLIC_ are exposed to the client. Sensitive keys (like API secrets or DB credentials) must not use NEXT_PUBLIC_ and should only be accessed on the server (API routes, getServerSideProps, or server components).Example:
What are some common security vulnerabilities in React apps and how do you prevent them?
What are some common security vulnerabilities in React apps and how do you prevent them?
1️⃣ XSS (Cross-Site Scripting):
Occurs when malicious scripts are injected into the DOM.✅ Fix:- Avoid
dangerouslySetInnerHTML - Sanitize input using libraries like DOMPurify
2️⃣ CSRF (Cross-Site Request Forgery):
Attackers trick users into making unwanted requests.✅ Fix:- Use anti-CSRF tokens (NextAuth and other libs handle this)
- Validate origin headers in API routes
3️⃣ Data Leakage via Source Code:
Leaking private tokens in frontend code.✅ Fix:- Never expose secrets in
NEXT_PUBLIC_vars - Validate .env usage during CI/CD
4️⃣ Insecure Direct Object Reference (IDOR):
Users access others’ data by manipulating IDs.✅ Fix:- Always verify authorization on the server
- Never trust frontend route params
How do you handle authentication securely in Next.js?
How do you handle authentication securely in Next.js?
Approach 1: Using NextAuth.js
What are HTTP-only cookies and why are they more secure?
What are HTTP-only cookies and why are they more secure?
How do you handle CORS securely?
How do you handle CORS securely?
How do you prevent open redirects in Next.js?
How do you prevent open redirects in Next.js?
How to secure API routes in Next.js?
How to secure API routes in Next.js?
What is CSP (Content Security Policy) and how does it protect your app?
What is CSP (Content Security Policy) and how does it protect your app?
Example:
How do you secure Next.js API endpoints with JWT?
How do you secure Next.js API endpoints with JWT?
How do you secure Next.js Server Actions or Route Handlers?
How do you secure Next.js Server Actions or Route Handlers?
What are some best practices for secure React + Next.js apps?
What are some best practices for secure React + Next.js apps?
| Category | Best Practice |
|---|---|
| 🔐 Authentication | Use JWT or NextAuth with HttpOnly cookies |
| 🔑 Secrets | Store in .env.local, never in client |
| 🧱 XSS | Sanitize HTML, avoid dangerouslySetInnerHTML |
| 🔄 CSRF | Use anti-CSRF tokens or same-site cookies |
| 🧭 Routing | Validate redirect URLs |
| 🧰 Packages | Audit dependencies with npm audit |
| ⚙️ Headers | Set CSP, X-Frame-Options, Strict-Transport-Security |
| 📦 API | Rate limit requests and validate input |
| 📡 HTTPS | Always use SSL in production |
11. Node.js Advanced Topics
What is Node.js?
What is Node.js?
- Traditional servers (Apache, Tomcat): One thread per connection. 10,000 concurrent users = 10,000 threads. Each thread consumes ~2MB of RAM. That is 20GB just for threads
- Node.js: Single thread + event loop + non-blocking I/O. 10,000 concurrent users share one thread. The event loop delegates I/O operations to the OS kernel or libuv’s thread pool, so the main thread is never blocked waiting for disk/network
- I/O-bound applications — APIs, real-time apps (chat, gaming), microservices, streaming
- Not ideal for CPU-bound tasks (image processing, video encoding, heavy computation) because they block the single thread. For these, use Worker Threads or offload to a different service
- Why is Node.js not recommended for CPU-intensive tasks? What happens to other requests when a CPU-heavy operation is running?
- How does Node.js handle 10,000 concurrent connections with a single thread?
- What is the difference between Node.js and Deno? Why did Ryan Dahl create Deno?
Explain the Node.js Event Loop. How does it handle asynchronous operations?
Explain the Node.js Event Loop. How does it handle asynchronous operations?
Event Loop Phases (TCCPPI)
- Timers – Executes callbacks from
setTimeout()andsetInterval() - Pending Callbacks – Executes I/O callbacks deferred from the previous cycle
- Idle / Prepare – Internal use
- Poll – Retrieves new I/O events; executes their callbacks
- Check – Executes callbacks from
setImmediate() - Close Callbacks – Executes callbacks like
socket.on('close')
Example
Why this order?
process.nextTick()runs before the event loop continues- Timers (
setTimeout) are queued for the Timers phase setImmediate()runs during the Check phase
When to use:
- Use
process.nextTick()for micro-tasks after the current operation - Use
setImmediate()for tasks after I/O events are processed
- Where do Promises fit in the event loop? Are they part of the microtask queue or the macrotask queue?
- What happens if you call
process.nextTick()recursively? How does it differ from recursivesetImmediate()? - Your API has an endpoint that takes 2 seconds to respond. 100 other requests are waiting. Is the event loop blocked? How can you tell?
What is the difference between process.nextTick() and setImmediate()?
What is the difference between process.nextTick() and setImmediate()?
- process.nextTick() executes before the event loop continues — a micro-task
- setImmediate() executes after the current poll phase — a macro-task
Example:
When to use:
- Use
process.nextTick()for quick callbacks that must run before I/O - Use
setImmediate()when you want to yield to I/O first to prevent blocking
Explain how Node.js handles non-blocking I/O
Explain how Node.js handles non-blocking I/O
Example:
Why:
The file read happens asynchronously; Node doesn’t block waiting for it.Where/When:
Used in high-throughput systems — e.g., API gateways, chat apps — where many concurrent requests are served without thread blocking.What are Streams in Node.js?
What are Streams in Node.js?
Types of Streams:
- Readable – e.g.
fs.createReadStream() - Writable – e.g.
fs.createWriteStream() - Duplex – both readable and writable (e.g. TCP socket)
- Transform – modifies data while reading/writing (e.g. zlib compression)
Example:
Why:
- Efficient for large files or live data (video, logs)
- Uses constant memory regardless of file size — a 10GB file uses the same ~64KB buffer whether you stream it or not
Where:
- File uploads/downloads (S3 streaming)
- Real-time data transfer (video/audio streaming)
- Log streaming and processing pipelines
- HTTP response streaming (server-sent events)
.pipe() automatically. When you use manual write() calls, you must handle backpressure yourself by checking the return value and listening for the drain event.What interviewers are really testing: Whether you understand memory-efficient data processing and can explain backpressure. This separates Node.js beginners from intermediate+ developers.Red flag answer: “Streams read files in chunks.” This misses backpressure, piping, and the four stream types.Follow-up:- What is backpressure in streams, and what happens if you ignore it?
- How would you implement a Transform stream that gzips data on the fly?
- When would you use
pipeline()from thestreammodule instead of.pipe()?
What is the difference between spawn, fork, and exec in Node.js?
What is the difference between spawn, fork, and exec in Node.js?
| Method | Description | Use Case |
|---|---|---|
| spawn | Launches a new process | For long-running or streaming output |
| exec | Launches a process and buffers entire output | For small output commands |
| fork | Special case of spawn that runs a Node.js script with IPC | For creating worker processes |
Example:
Why/When/Where:
- Use spawn for streaming output (e.g., logs)
- Use exec when you need full command output as a string
- Use fork for scaling CPU-bound tasks or worker threads
What are Worker Threads and how do they differ from the Event Loop?
What are Worker Threads and how do they differ from the Event Loop?
Example:
Why:
To offload heavy computations (e.g. image processing, encryption) so they don’t block the main event loop.Where:
In apps with mixed I/O and CPU workloads, like video encoding servers.How do you handle uncaught exceptions and unhandled promise rejections?
How do you handle uncaught exceptions and unhandled promise rejections?
Example:
Why:
Prevents app from crashing unexpectedly and allows logging/restarting.When/Where:
Use this for graceful shutdown and to catch programming mistakes in production.Explain Cluster Mode in Node.js and how it helps in scaling
Explain Cluster Mode in Node.js and how it helps in scaling
Example:
Why:
- Single Node.js process uses only one CPU core
- Cluster mode scales horizontally across all cores
Where:
Used in production APIs — e.g., Express servers, GraphQL APIs — to handle more traffic efficiently.How does Node.js handle memory management?
How does Node.js handle memory management?
Common causes of leaks:
- Global variables
- Unclosed timers or listeners
- Caching large data objects indefinitely
Example Leak:
When/Where:
Monitor memory with tools like:--inspect+ Chrome DevTools- clinic.js, heapdump,
node --trace-gc
What are Buffers in Node.js?
What are Buffers in Node.js?
Example:
Why:
Buffers allow manipulation of binary data efficiently (e.g., file systems, TCP streams).Where:
Used in file operations, network protocols, and binary serialization.What are Modules in Node.js and how are they resolved?
What are Modules in Node.js and how are they resolved?
Example:
Module Resolution Order:
- Core modules (fs, path)
- Local files (./, ../)
- node_modules directory
Why:
Encapsulation of code — prevents global scope pollution.Where:
Used in all Node.js apps; ES Modules preferred for modern codebases.How do you structure a large-scale Node.js application?
How do you structure a large-scale Node.js application?
Common Structure:
Layers Explained:
- Controller: Handles request/response (HTTP logic)
- Service: Business logic
- Model: Database schema and queries
- Middleware: Reusable pre-processing (auth, validation)
- Routes: Maps endpoints to controllers
Why:
- Improves separation of concerns
- Enables unit testing per layer
- Simplifies onboarding and scalability
Where/When:
Used in all enterprise-level Node.js APIs or microservices where team collaboration and modularization are essential.What are some common design patterns used in Node.js?
What are some common design patterns used in Node.js?
Key Patterns:
| Pattern | Description | Example | Use Case |
|---|---|---|---|
| Singleton | Single shared instance | DB connection pool | Database connections |
| Factory | Creates objects dynamically | Model creation | Dynamic service instantiation |
| Observer | Event-driven | EventEmitter | Real-time systems |
| Middleware | Chain of functions | Express middlewares | API requests |
| Repository | Abstract data layer | Repository class | Decoupled DB logic |
| Decorator | Adds behavior without altering | Wrapping services | Logging, caching |
Singleton Pattern Example:
When: Use for connection pools, config objects, caches
Factory Pattern Example:
When: Use for service selection (e.g., multiple gateways, APIs)
What is MVC (Model-View-Controller) and how is it used in Node.js?
What is MVC (Model-View-Controller) and how is it used in Node.js?
- Model: Manages data and database operations
- View: Renders UI (in REST APIs, often JSON)
- Controller: Handles requests, uses Model to get data, and sends responses
Example:
Why:
Encourages separation of concerns, clean testing, and reusability.Where/When:
Common in Express-based web APIs and server-rendered apps.How would you design a scalable microservices architecture in Node.js?
How would you design a scalable microservices architecture in Node.js?
Key Components:
- Each service has own database, own deployment
- Communication via REST, gRPC, or message queues (e.g., RabbitMQ, Kafka)
- Use API Gateway for centralized routing/auth
Example Setup:
Example Communication:
Why:
- Independent scaling and deployment
- Easier fault isolation
- Better for large teams or multi-domain systems
When/Where:
Used in enterprise systems (e.g., eCommerce, SaaS) where modularity and scaling are critical.How do microservices communicate with each other?
How do microservices communicate with each other?
| Type | Example | Use Case |
|---|---|---|
| Synchronous (Request-Response) | REST, gRPC | Real-time data |
| Asynchronous (Event-driven) | RabbitMQ, Kafka, Redis Pub/Sub | Decoupled systems |
Asynchronous Messaging Example (RabbitMQ):
Why:
Async messaging improves resilience and fault tolerance.When/Where:
Use async communication when loose coupling is desired (e.g., sending notifications after order creation).Explain Repository Pattern and why it's useful
Explain Repository Pattern and why it's useful
Example:
Why:
- Makes business logic database-agnostic
- Simplifies unit testing (mock repositories)
- Promotes clean architecture
When/Where:
Used in large teams where database changes should not affect business logic.What is Dependency Injection and how can it be applied in Node.js?
What is Dependency Injection and how can it be applied in Node.js?
Example:
Why:
- Enables loose coupling and easier testing
- Supports inversion of control
When/Where:
Used in testable architectures, e.g., NestJS framework (which has DI built-in).How do you handle configuration management in Node.js across environments?
How do you handle configuration management in Node.js across environments?
Example Structure:
dotenv or config.Example with dotenv:
Why:
- Keeps secrets out of source code
- Easier environment portability
Where/When:
In CI/CD pipelines and multi-environment deployments (e.g., AWS, Docker).What is a layered architecture in backend design?
What is a layered architecture in backend design?
Typical Layers:
- Presentation (Controller) – Handles requests
- Business (Service) – Contains logic
- Data (Repository) – Handles persistence
- Integration (External APIs) – Handles external comms
Example Flow:
Why:
- Makes the system modular, testable, and maintainable
Where:
Used in enterprise backends and API-first architectures.How would you handle versioning in REST APIs?
How would you handle versioning in REST APIs?
Ways to Version:
- URL-based:
/api/v1/users - Header-based:
Accept: application/vnd.api.v2+json - Query-based:
/users?version=2
Example:
Why:
Maintains backward compatibility and smooth client migration.When/Where:
Use when rolling out breaking API changes.Explain how you'd implement CQRS (Command Query Responsibility Segregation)
Explain how you'd implement CQRS (Command Query Responsibility Segregation)
What are modules in Node.js and why are they important?
What are modules in Node.js and why are they important?
Node.js supports three main types of modules:
- Core Modules (built-in) → e.g., fs, path, http
- Local Modules → custom files in your app
- Third-party Modules → installed from npm (e.g., express, lodash)
Example:
Why:
To promote reusability and separation of concerns. Without modules, everything would live in one file, making maintenance and testing hard.When:
Use modules when splitting logic — routes, services, utilities, models, etc.Where:
Common in every medium to large-scale Node.js app, especially with MVC or layered architecture.Explain the difference between CommonJS (require) and ES Modules (import)
Explain the difference between CommonJS (require) and ES Modules (import)
| Feature | CommonJS (require) | ES Modules (import) |
|---|---|---|
| Loading | Synchronous | Asynchronous |
| Syntax | const x = require('x') | import x from 'x' |
| Scope | Wrapped in function | Strict top-level |
| Default in | Node.js before v14 | Modern Node.js (with “type”: “module”) |
Example:
Why:
ESM is the modern standard (tree-shaking, async loading, static analysis).When:
- Use CommonJS in legacy or mixed codebases
- Use ESM for modern projects (especially with TypeScript or Next.js APIs)
Where:
Configured via package.json →"type": "module"What is the difference between MVC and layered architecture in Node.js?
What is the difference between MVC and layered architecture in Node.js?
| Concept | MVC | Layered Architecture |
|---|---|---|
| Pattern | Model–View–Controller | Request–Controller–Service–Repository |
| Purpose | Web apps with UI | APIs and backend systems |
| Focus | Separation of UI and logic | Logical separation by responsibility |
Example Layered Architecture Flow:
Why:
Each layer has a single responsibility — easy to test and change independently.When:
For scalable APIs and microservices.Where:
Used in enterprise Node.js apps (Express, NestJS, Fastify-based systems).How do you manage environment variables in Node.js?
How do you manage environment variables in Node.js?
Example:
Why:
Keeps sensitive data (like DB passwords, API keys) out of code.When:
Always use environment variables for configuration.Where:
process.env→ globally accessible- Used in CI/CD pipelines, Docker, Kubernetes secrets, etc.
What is dependency injection and why is it useful in Node.js architecture?
What is dependency injection and why is it useful in Node.js architecture?
Example:
Why:
- Makes testing easier (can inject mocks)
- Improves flexibility and maintainability
When:
Used heavily in frameworks like NestJS or in test-driven architectures.Where:
Service layers, repositories, utilities, or external integrations.How do you handle circular dependencies in Node.js modules?
How do you handle circular dependencies in Node.js modules?
Example:
Solution:
- Refactor shared logic into a new module
- Use dependency injection
- Lazy-load the dependency inside a function
Why:
To prevent runtime bugs due to incomplete module initialization.When:
When modules reference each other’s exports.Where:
Common in large codebases with intertwined controllers/services.What is the EventEmitter pattern and how is it used in Node.js?
What is the EventEmitter pattern and how is it used in Node.js?
Core API:
Real-world production use case — decoupled business logic:
on(event, listener)— subscribe (alias:addListener)once(event, listener)— subscribe for one event onlyemit(event, ...args)— trigger all listeners for an eventremoveListener(event, listener)— unsubscribesetMaxListeners(n)— default is 10; exceeding it prints a memory leak warning
- How do you prevent memory leaks caused by forgotten event listeners?
- What is the difference between
onandonce? When would you useonce? - How does EventEmitter relate to Node.js streams?
How do you implement graceful shutdown in a Node.js server?
How do you implement graceful shutdown in a Node.js server?
Production-grade implementation:
- Kubernetes sends SIGTERM before killing a pod. Without graceful shutdown, active requests get 502 errors
- PM2 sends SIGINT before restarting. Without graceful shutdown, database connections may not close properly, causing connection pool exhaustion
- Load balancers need time to drain connections from the old instance before routing to the new one
- Stop accepting new connections (
server.close()) - Finish in-flight requests (they complete normally)
- Close external connections (DB, Redis, message queues)
- Exit the process
node app.js locally.Red flag answer: “Just call process.exit(0).” This kills everything immediately, including in-flight requests and open database transactions.Follow-up:- What signal does Kubernetes send before killing a pod, and how much time do you have to shut down?
- How do you handle long-running WebSocket connections during graceful shutdown?
- What happens if a database operation is mid-transaction when SIGTERM is received?
How do you organize routes in an Express application?
How do you organize routes in an Express application?
What are 'config', 'utils', and 'helpers' folders used for?
What are 'config', 'utils', and 'helpers' folders used for?
| Folder | Purpose | Example |
|---|---|---|
| config | Centralized configurations | DB, environment setup |
| utils | Reusable functions | formatters, loggers, date handlers |
| helpers | Request-specific helper logic | validation, response shaping |
Example:
Why:
Encourages DRY (Don’t Repeat Yourself) coding.When:
Used in any mid-size project to centralize repetitive logic.Where:
Globally accessible via imports across layers.How do you separate concerns in a Node.js microservice architecture?
How do you separate concerns in a Node.js microservice architecture?
- Each service owns a single business domain
- Communicates via APIs, queues, or message brokers (e.g., RabbitMQ, Kafka)
- Uses its own database
Example Architecture:
Why:
To achieve scalability, fault isolation, and independent deployment.When:
When the app grows large and needs distributed scaling.Where:
Used in large enterprise systems (e.g., Uber, Netflix architectures).12. Node.js Testing and Debugging
Why is testing important in backend development, and what types of testing do you perform in Node.js projects?
Why is testing important in backend development, and what types of testing do you perform in Node.js projects?
Types of testing in Node.js:
-
Unit Testing – Tests individual functions or modules
- Tools: Jest, Mocha, Chai
- Example: testing a utility function like
calculateTax()
-
Integration Testing – Tests multiple components working together
- Example: testing API endpoints interacting with database and services
-
End-to-End (E2E) Testing – Simulates real user scenarios
- Tools: Supertest, Cypress
- Regression Testing – Ensures new code doesn’t break existing functionality
-
Performance / Load Testing – Evaluates API performance under stress
- Tools: Artillery, K6
Why:
Improves code reliability and confidence in deployment.When:
After each module development or before merging into main branch.Where:
Applied across APIs, database queries, and business logic layers.What is Unit Testing and how do you implement it in Node.js?
What is Unit Testing and how do you implement it in Node.js?
Why:
To verify that each unit of your code performs as intended without depending on external systems.When:
During development or before integration.Example using Jest:
Where:
Stored inside a__tests__ folder or with .test.js extension inside the same module directory.How do you test API endpoints in a Node.js app?
How do you test API endpoints in a Node.js app?
How do you mock external dependencies or services during testing?
How do you mock external dependencies or services during testing?
Why:
To ensure tests run fast, deterministically, and don’t depend on network or environment.When:
When your code calls APIs, databases, or third-party SDKs.Example using Jest Mocks:
Where:
In any module that uses third-party dependencies like AWS SDK, Stripe, or Axios.How do you debug a Node.js application?
How do you debug a Node.js application?
Common methods:
-
Console logging:
Quick and easy but not ideal for large projects.
-
Node Inspector / Chrome DevTools:
Run app with:
Then open
chrome://inspect→ Attach debugger → Add breakpoints. -
VS Code Debugger:
Add a launch.json config:
-
PM2 Logs:
For production debugging:
When:
During development or after reproducing a bug reported from QA.Where:
You can debug application logic, event loops, or async operations.How do you handle errors gracefully in Node.js applications?
How do you handle errors gracefully in Node.js applications?
What are some common debugging tools or libraries used in Node.js?
What are some common debugging tools or libraries used in Node.js?
- Node.js Inspector – built-in debugger for step-through debugging
- Chrome DevTools – UI-based debugging
- VS Code Debugger – integrated IDE tool
- PM2 – process manager for logs and metrics
- Clinic.js – performance profiler
- Winston / Pino – structured logging libraries
Why:
They help in isolating performance bottlenecks, memory leaks, and runtime errors.Where:
Use locally (VS Code/Chrome) or in production (PM2, Winston).How do you ensure test coverage and integrate testing into CI/CD?
How do you ensure test coverage and integrate testing into CI/CD?
Tool: Jest provides built-in coverage reports.
CI/CD Integration (GitHub Actions example):
Why:
Automatically ensures all commits pass tests before merging.When:
Triggered on every pull request or push to main branch.How do you debug a memory leak in Node.js?
How do you debug a memory leak in Node.js?
Detection Steps:
-
Monitor heap usage:
-
Use Chrome DevTools:
- Run
node --inspect - Open Heap Snapshots → Compare over time
- Run
- Use Clinic.js or Memwatch-next
Common causes:
- Global variables
- Unclosed timers
- Unreleased event listeners
Fix:
- Use WeakMap for temporary references
- Remove listeners:
- Close database connections
13. Database Design
How do you decide between SQL and NoSQL for a project?
How do you decide between SQL and NoSQL for a project?
| SQL (Relational) | NoSQL (Document/Key-Value) |
|---|---|
| Structured schema (tables, columns) | Flexible schema (JSON, documents) |
| Strong relationships (JOINs) | Denormalized, nested data |
| ACID transactions | Eventual consistency |
| Example: PostgreSQL, MySQL | Example: MongoDB, DynamoDB |
Example Use Cases:
- SQL: Financial systems, HR platforms (strong relationships)
- NoSQL: E-commerce product catalogs, social feeds (flexible schema)
Why:
SQL enforces strict integrity, NoSQL provides scalability.When:
Choose SQL when data relations are strong; NoSQL for fast-growing, schema-less data.Where:
SQL → transactional layer; NoSQL → analytics or caching layer.Explain normalization and denormalization. When would you use each?
Explain normalization and denormalization. When would you use each?
- Normalization: Process of organizing data to reduce redundancy and improve consistency
- Denormalization: Combining related data into a single structure to improve read performance
Example:
Normalized (two tables):Why:
Normalization improves consistency; denormalization improves read speed.When:
Normalize for frequent writes, denormalize for heavy reads.Where:
E.g., OLTP → normalized; OLAP/NoSQL → denormalized.What is an index? How does it improve performance, and when can it hurt?
What is an index? How does it improve performance, and when can it hurt?
Explain transactions in databases. How are they handled in Node.js?
Explain transactions in databases. How are they handled in Node.js?
Example (PostgreSQL with Sequelize):
Why:
Prevents partial updates when one step fails.When:
Use for multi-table or dependent operations (e.g., payments, inventory).Where:
Implement in service layer functions handling multi-step DB operations.How do you optimize slow database queries?
How do you optimize slow database queries?
- Use
EXPLAINorexplain()to inspect query execution plan - Add proper indexes
- Avoid
SELECT *; specify columns - Paginate large queries
- Cache repetitive queries
Example (MongoDB):
Example (Redis caching):
Why:
Optimized queries save cost and improve API response times.When:
Apply during scaling or under heavy load.Where:
Inside data-access layer (repositories).What are database relationships and how do you handle them in Node.js?
What are database relationships and how do you handle them in Node.js?
- One-to-One: User ↔ Profile
- One-to-Many: User → Orders
- Many-to-Many: Students ↔ Courses
Example (MongoDB - embedding vs referencing):
Why:
Embedding improves read speed; referencing saves space.When:
Embed for frequent reads; reference for frequent writes.Where:
Schema design phase, based on access patterns.How do you handle pagination efficiently?
How do you handle pagination efficiently?
What are database migrations and why are they important?
What are database migrations and why are they important?
Example (with Sequelize):
Example (migration file):
Why:
Ensures consistent schema across environments (dev, staging, prod).When:
Whenever adding/removing/modifying columns or constraints.Where:
Stored in/migrations folder, managed by ORM/CLI.How do you scale databases in high-traffic applications?
How do you scale databases in high-traffic applications?
- Read replicas — offload read traffic
- Sharding — partition data by key (e.g., userId)
- Caching — Redis/Memcached for frequent reads
- Connection pooling — reuse DB connections
Example:
Why:
Prevents bottlenecks under high load.When:
Beyond 10k+ users or concurrent reads.Where:
Database + ORM configuration level.How do you ensure data consistency in distributed systems?
How do you ensure data consistency in distributed systems?
Patterns:
- Two-phase commit (2PC) for strict consistency
- Eventual consistency for scalability
- Sagas pattern for distributed transactions
Example (Sagas):
Why:
Keeps data correct even across microservices.When:
In event-driven or microservice architectures.Where:
Implement at service orchestration level.What are ACID and BASE principles?
What are ACID and BASE principles?
| ACID (SQL) | BASE (NoSQL) |
|---|---|
| Atomicity | Basically Available |
| Consistency | Soft-state |
| Isolation | Eventual consistency |
| Durability |
Why:
ACID ensures reliable transactions; BASE ensures availability at scale.When:
Use ACID for critical systems (banking), BASE for high-volume systems (social media).Where:
Choose based on business priority: consistency vs availability.What is the N+1 query problem and how do you solve it?
What is the N+1 query problem and how do you solve it?
Example of the problem:
Solutions:
1. Population/Join (MongoDBpopulate or SQL JOIN):- How would you detect N+1 queries in a production Node.js application?
- Does MongoDB’s
populate()actually solve N+1, or does it just hide it? How many queries does it actually execute? - How does the DataLoader pattern batch and deduplicate queries in a GraphQL API?
What is database connection pooling and why is it critical in Node.js?
What is database connection pooling and why is it critical in Node.js?
Without pooling (bad):
With pooling (good):
poolSize = (number of CPU cores * 2) + number of disks- Too few connections: requests queue up waiting for a connection
- Too many connections: database server is overwhelmed (MongoDB has a default 1024 connection limit)
- A typical Node.js service with 4 cores should start with 10-20 connections
- Node.js handles thousands of concurrent requests with one thread
- Each concurrent request that needs a DB query needs a connection
- Without pooling, 1000 concurrent requests create 1000 connections — most databases cannot handle this
- Your app starts throwing “connection pool exhausted” errors during peak traffic. How do you diagnose and fix it?
- How do you handle database connection pool in a serverless environment (Lambda, Vercel Functions)?
- What is the connection pool behavior difference between Mongoose and the native MongoDB driver?
14. API Design & Best Practices
What are RESTful APIs and their main principles?
What are RESTful APIs and their main principles?
Core Principles:
- Statelessness → Server doesn’t store client state between requests
- Uniform Interface → Consistent structure for all endpoints (
/users,/products) - Client-Server Separation → Independent evolution of frontend & backend
- Cacheable → Responses can be cached for performance
- Layered System → Requests pass through intermediaries like load balancers or proxies
Example:
Why:
REST’s simplicity makes it ideal for large-scale distributed systems. The uniform interface means any developer can understand your API without reading implementation code.When:
Use for stateless communication (e.g., SaaS apps, mobile backends, third-party integrations). REST is the default choice for CRUD-heavy APIs.Where:
Commonly implemented with Express.js, NestJS, or Fastify. Consumed by React/Next.js frontends, mobile apps, and other services.Common REST anti-patterns to avoid:- Verb in the URL:
/api/getUsersor/api/createUser— HTTP methods already convey the action - Inconsistent naming:
/api/Usersvs/api/user-profilesvs/api/orders_list— pick one convention (plural nouns, kebab-case is standard) - Returning 200 for everything: Even errors return
200 OKwith{ "error": true }in the body. Use proper HTTP status codes - No pagination: Returning all 50,000 records on a list endpoint
- What makes an API truly “RESTful” vs just “REST-like”? What is the Richardson Maturity Model?
- How do you handle actions that do not map cleanly to CRUD? For example, “send a password reset email.”
- What is HATEOAS and why is it rarely implemented in practice?
How would you design an API for scalability and maintainability?
How would you design an API for scalability and maintainability?
Use modular folder structure:
- Follow Controller-Service-Repository pattern
- Use async/await and central error handling
- Add pagination, filtering, sorting
Example:
Why:
Separation of concerns ensures scalability and testability.When:
For medium to large projects.Where:
Apply across all route modules for consistency.What's the difference between PUT and PATCH?
What's the difference between PUT and PATCH?
| Method | Purpose | Example |
|---|---|---|
| PUT | Replace the entire resource | { "name": "Ali", "email": "x@x.com" } |
| PATCH | Update part of a resource | { "email": "new@x.com" } |
Example:
Why:
Use PATCH for partial updates to avoid overwriting data.When:
Frontend sends only changed fields.Where:
In APIs supporting user profile updates, etc.How do you handle errors and validation in REST APIs?
How do you handle errors and validation in REST APIs?
How do you handle versioning in APIs?
How do you handle versioning in APIs?
How would you secure an API?
How would you secure an API?
- Authentication & Authorization (JWT, OAuth2)
- Input validation (prevent SQL/NoSQL injection)
- Rate limiting (prevent brute-force)
- CORS control
- HTTPS for encryption
Example (rate limiting):
Why:
Prevents abuse, leaks, and attacks.When:
Always — security should be baked in early.Where:
Applied globally or per route.What are idempotent methods in REST APIs?
What are idempotent methods in REST APIs?
| Method | Idempotent? | Example |
|---|---|---|
| GET | ✅ Yes | Fetching user |
| PUT | ✅ Yes | Updating same data |
| DELETE | ✅ Yes | Deleting same resource again |
| POST | ❌ No | Creates a new record each time |
Example:
Why:
Idempotency ensures reliability in retry scenarios.When:
Especially in payment APIs or distributed systems.Where:
Route and controller logic level.How would you design pagination, filtering, and sorting?
How would you design pagination, filtering, and sorting?
How do you document APIs effectively?
How do you document APIs effectively?
How would you handle rate limiting and throttling?
How would you handle rate limiting and throttling?
What are some common API response codes and their meanings?
What are some common API response codes and their meanings?
| Code | Meaning | Use Case |
|---|---|---|
| 200 | OK | Successful GET |
| 201 | Created | Resource created (POST) |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation error |
| 401 | Unauthorized | Missing/invalid token |
| 403 | Forbidden | Access denied |
| 404 | Not Found | Resource doesn’t exist |
| 500 | Internal Server Error | Unexpected error |
Example:
Why:
Clear status codes improve debugging and API usability.When:
Always respond with meaningful HTTP codes.Where:
Controller layer.When would you choose GraphQL over REST?
When would you choose GraphQL over REST?
| REST | GraphQL |
|---|---|
| Multiple endpoints | Single endpoint /graphql |
| Fixed response shape | Client defines response |
| Over-fetching common | Fetch only needed fields |
| Simpler caching | Complex but flexible queries |
Example:
Why:
GraphQL avoids under/over-fetching and gives the frontend team independence from the backend team’s endpoint design. In a REST API, the frontend often needs to request 3-4 endpoints and stitch data together. GraphQL does this in one request.When:
- Choose GraphQL for: complex UIs with diverse data needs (dashboards, social feeds), mobile apps where bandwidth is precious, or when multiple frontend teams consume the same API with different data requirements
- Choose REST for: simple CRUD APIs, public APIs (REST is universally understood), webhooks, file uploads, or when caching is critical (REST + CDN is simpler than GraphQL caching)
Where:
Use Apollo Server, GraphQL Yoga, or Mercurius (Fastify) with Node.js. On the client, Apollo Client or urql.The hidden cost of GraphQL:- Query complexity attacks: Without depth limiting, a client can send a deeply nested query that takes down your server. Use
graphql-depth-limitandgraphql-query-complexity - N+1 problem amplified: Resolvers execute per-field, so a list of 100 users with their orders fires 100 individual order queries. DataLoader is mandatory
- Caching is harder: REST endpoints are easily cached by CDNs (same URL = same response). GraphQL POST requests all go to
/graphqlwith different bodies, making CDN caching ineffective - Schema management overhead: Maintaining a GraphQL schema, resolvers, and type generation (codegen) is more work than REST endpoints
- How do you prevent malicious GraphQL queries from overloading your server?
- What is the DataLoader pattern and why is it essential for GraphQL performance?
- Can you use GraphQL and REST in the same project? When would you want to?
15. Caching and Performance Optimization
What is caching and why is it important in Node.js applications?
What is caching and why is it important in Node.js applications?
Why:
- Reduces response time
- Minimizes database load
- Improves scalability and user experience
When to use:
When you have repetitive, read-heavy operations such as:- Fetching static product details
- Returning popular posts
- Computing costly aggregations
Where:
Typically used:- Between the API and the database
- At CDN level for static assets
- In-memory (e.g., Redis, Node cache) for API data
Example:
What are different types of caching in Node.js?
What are different types of caching in Node.js?
1. In-memory cache:
- Stored in Node process memory using packages like node-cache or lru-cache
- Fastest but not shared across multiple server instances
- Best for: Single-instance apps, computed results
2. Distributed cache (Redis / Memcached):
- External in-memory databases shared across multiple app instances
- Best for: Scalable and load-balanced applications
3. Browser caching / CDN caching:
- For static files like images, scripts, and CSS
- Best for: Reducing load time for front-end users
4. Application-level caching:
- Using HTTP headers like Cache-Control, ETag, or response-level caching middleware
How do you identify performance bottlenecks in a Node.js app?
How do you identify performance bottlenecks in a Node.js app?
Node’s built-in profiler:
Performance monitoring tools:
- PM2 monitoring dashboard
- New Relic, Datadog, or AppDynamics for production-level insights
Steps to identify bottlenecks:
- Measure response time and CPU usage
- Analyze event loop lag (using clinic.js or node —inspect)
- Check for slow database queries and missing indexes
- Profile memory leaks via heap snapshots
When:
Always perform during load testing before production deployment.How do you optimize a Node.js application for better performance?
How do you optimize a Node.js application for better performance?
1. Use asynchronous code properly:
Avoid blocking the event loop by using non-blocking async operations.2. Enable GZIP compression:
Compress HTTP responses using middleware like compression.3. Use Redis or in-memory cache:
Cache frequent DB queries to reduce load.4. Cluster your Node process:
Utilize multiple CPU cores using cluster or PM2.5. Optimize database queries:
Use proper indexes, projections, and pagination.6. Use streaming for large data:
Instead of loading entire data into memory.7. Use load balancing & reverse proxies (Nginx):
Helps distribute load across multiple Node instances.What is Redis and how do you use it for caching in Node.js?
What is Redis and how do you use it for caching in Node.js?
- Caching responses
- Session storage
- Pub/Sub systems
- Rate limiting
Why Redis:
- Very fast (stores data in RAM)
- Persistent (can save snapshots to disk)
- Supports TTL (time-to-live) expiration
Example:
When:
When your system frequently requests the same data from a slow data source like MongoDB or an external API.What is event loop blocking and how does it impact performance?
What is event loop blocking and how does it impact performance?
How do you handle rate limiting and throttling for performance?
How do you handle rate limiting and throttling for performance?
How do you measure memory usage and detect memory leaks in Node.js?
How do you measure memory usage and detect memory leaks in Node.js?
- Run app with
node --inspect - Open
chrome://inspect - Take Heap Snapshots and compare over time
When leaks occur:
- Global variables not freed
- Large cached data without TTL
- Event listeners not removed
Where to fix:
Ensure proper cleanup using:Advanced Scenario-Based Questions
These questions simulate real production incidents and architectural decisions that separate candidates who have actually built and operated MERN applications at scale from those who have only completed tutorials.Scenario 1: MongoDB Query Performance Collapse at Scale
Scenario 1: MongoDB Query Performance Collapse at Scale
/api/products/search endpoint has degraded from 80ms to 6.2 seconds at p95. The collection has individual indexes on tags, price, and rating. CPU on your Atlas M40 cluster is pegged at 92%. The PM is asking why search is broken and wants it fixed by EOD. Walk me through your diagnosis and fix.What weak candidates say:
“I’d add more indexes or increase the server size. Maybe we need to shard the collection.”What strong candidates say:- The core issue is almost certainly index intersection inefficiency. MongoDB can combine multiple single-field indexes via index intersection, but it is wildly unpredictable and almost always slower than a proper compound index for multi-field queries. With 12M documents, the query planner is likely doing a collection scan or picking one index and then scanning millions of results for the other predicates.
- Step 1 - Confirm with
explain("executionStats"): Run the exact query the API fires with.explain("executionStats")and look attotalDocsExaminedvstotalKeysExaminedvsnReturned. IftotalDocsExaminedis in the millions butnReturnedis in the hundreds, you have an index selectivity problem. - Step 2 - Check with
$indexStats: Rundb.products.aggregate([{$indexStats: {}}])to see which indexes are actually being used and how often. Bet the compound query is falling back to a single-field index scan. - Step 3 - Build a compound index: Create
db.products.createIndex({tags: 1, rating: -1, price: 1})putting the equality match field first (tags), then the sort/range field (ratingdescending since users want highest first), then the secondary range field (price). This follows the ESR rule: Equality, Sort, Range. - Step 4 - Drop the redundant single-field indexes on
tagsandrating(the compound index covers those prefixes). Keep thepriceindex only if other queries use it in isolation. - Step 5 - Monitor with Atlas Performance Advisor or
db.currentOp()for slow queries after deployment. Target should be under 50ms at p95. - Real-world gotcha: Building an index on 12M docs will lock writes if you use foreground indexing. Always use
background: truein older MongoDB versions. In MongoDB 4.2+ index builds are hybrid by default, but they still consume I/O. Schedule it during low-traffic hours or use a rolling index build across replica set members. - The sharding suggestion is a red flag at this scale. 12M documents is well within a single replica set capability. Sharding adds massive operational complexity and should only be considered when you are past several hundred million documents or your working set exceeds available RAM.
tags field is an array with an average of 8 tags per product?Follow-up: The fix worked but the PM now wants full-text search with typo tolerance across product names and descriptions. Do you keep this in MongoDB or reach for something else? What are the trade-offs of MongoDB Atlas Search vs Elasticsearch vs Meilisearch?Follow-up: You discover that 40% of your queries are range queries on price alone without tags. How do you handle index design when different query patterns compete for optimization?Scenario 2: React Component Re-renders Destroying Frame Rate
Scenario 2: React Component Re-renders Destroying Frame Rate
Dashboard then DataTable then DataRow (x200). How do you fix this?What weak candidates say:
“I’d use React.memo on everything and add useMemo and useCallback everywhere.”What strong candidates say:- Blanket
React.memois the spray-and-pray approach. It helps, but without understanding why things re-render, you will just shift the bottleneck. Here is a systematic approach: - Root cause diagnosis: The WebSocket handler likely sets state at the
Dashboardlevel with something likesetRows(newRows), which replaces the entire array reference. Even if only 3 rows changed, React sees a new array and re-renders everything downstream. - Fix 1 - Normalize state and update surgically: Instead of storing rows as an array, use a
Mapor object keyed by row ID. When a WebSocket message arrives, only update the specific rows that changed:
- Fix 2 -
React.memoonDataRowwith proper comparison: WrapDataRowinReact.memobut ensure props are stable. If you pass callbacks, they must be wrapped inuseCallback. If you pass objects, they need referential stability. - Fix 3 - Virtualization: 200 rows is borderline, but if each row has complex cells, use
react-windowor@tanstack/virtual. Only render the ~20 rows visible in the viewport. This alone can cut render time by 90%. - Fix 4 - Decouple subscription from React state: For truly high-frequency updates, consider using a
useRef+requestAnimationFramepattern where the WebSocket writes to a ref and a rAF loop reads from it to batch visual updates. Or use a state manager like Zustand which allows component-level subscriptions where eachDataRowsubscribes to only its own slice of data. - Measurement: After each fix, check the Profiler “Highlight updates when components render” and the Performance tab frame rate graph. Target: render time under 8ms to leave headroom for browser paint.
- What to avoid:
shouldComponentUpdatewith deep comparison on 200 rows because the comparison cost itself becomes the bottleneck. Also avoidJSON.stringifycomparisons. That is O(n) on object size for every render cycle.
react-konva. When would canvas-based rendering actually make sense over DOM-based React rendering?Follow-up: How would you approach this differently if you were using React Server Components and the data needed to be real-time?Scenario 3: Node.js Memory Leak in Production
Scenario 3: Node.js Memory Leak in Production
- Periodic restarts are a band-aid that masks the root cause. Here is a systematic approach:
- Step 1 - Confirm it is a heap leak, not native memory: Run
process.memoryUsage()on a timer and logheapUsed,heapTotal,rss, andexternal. IfheapUsedgrows linearly, it is a JS heap leak. Ifrssgrows butheapUsedis stable, it is a native addon, Buffer, or stream leak which is a different debugging path entirely. - Step 2 - Take heap snapshots in production: Use the
--inspectflag orv8.writeHeapSnapshot()triggered via a debug endpoint (protected behind auth). Take one snapshot at startup and another after 24 hours. Load both into Chrome DevTools Memory tab and use the “Comparison” view to see what objects accumulated. - Step 3 - The “activity tracking” middleware is the prime suspect. Common leak patterns in middleware:
- Closures capturing request objects: If the middleware stores
reqorresreferences in a module-level array, Map, or Set for later processing, and those references are never cleaned up. - Event listeners accumulating: If it attaches listeners to a shared EventEmitter on every request without removing them. Node will warn with “MaxListenersExceededWarning” but check if this warning was suppressed with
setMaxListeners(0), which is a classic antipattern. - Unbounded in-memory cache: If it caches user activity data in a plain object or Map without TTL or size limits.
- Closures capturing request objects: If the middleware stores
- Step 4 - Likely fix: Based on the middleware pattern, look for something like:
- Fix: Either flush to database/Redis periodically and clear the Map, use an LRU cache with a max size (
lru-cachepackage), or better yet, do not accumulate in memory at all. Write directly to a log stream or message queue. - Step 5 - Verify the fix: Deploy and watch the memory graph. Healthy Node apps should have a sawtooth pattern (heap grows, GC collects, drops back) rather than a linear climb.
- Production tooling to set up: Use
clinic.js(clinic doctorandclinic heap) for automated analysis. In production, add a/debug/memoryendpoint that returnsprocess.memoryUsage()and triggerglobal.gc()(with--expose-gcflag) to differentiate between “has not GC’d yet” and “cannot GC.”
IncomingMessage objects. But the middleware does not explicitly store req. What other patterns could cause request objects to be retained?Follow-up: You fixed the leak, but now you notice the GC pause times spike to 200ms every few minutes, causing p99 latency spikes. How do you tune V8 garbage collection for a high-throughput API server?Follow-up: How would you set up proactive memory leak detection in your CI/CD pipeline so this does not reach production again?Scenario 4: Express API Security Breach Post-Mortem
Scenario 4: Express API Security Breach Post-Mortem
/api/users?role=admin and /api/users/../../etc/passwd. Your API has JWT authentication but no further authorization layer. The team asks you to do a post-mortem and harden the API. Walk through your analysis and remediation plan.What weak candidates say:
“I’d add input validation and make sure the JWT is checked on every route.”What strong candidates say:- This breach has at least three distinct vulnerabilities that need separate fixes. Here is the breakdown of each attack vector:
- Vulnerability 1 - Broken Access Control (BOLA/IDOR): The
/api/users?role=adminendpoint likely returns all users matching the query without checking whether the requesting user has permission to see those records. This is OWASP #1 Broken Access Control. The JWT proves who you are (authentication) but nothing checks what you are allowed to access (authorization).- Fix: Implement authorization middleware that checks the requesting user role/permissions before executing queries. Never trust query parameters for access control:
- Vulnerability 2 - Path Traversal: The
/api/users/../../etc/passwdrequest is a classic path traversal attack. This suggests the app is using a user-supplied parameter to construct file paths or that the router is not sanitizing path parameters.- Fix: Never construct file paths from user input. If you must, use
path.resolve()and verify the result stays within an allowed directory. Also, addhelmetmiddleware which sets security headers, and useexpress-mongo-sanitizeto prevent NoSQL injection via query parameter manipulation.
- Fix: Never construct file paths from user input. If you must, use
- Vulnerability 3 - Mass Assignment / Query Injection: The fact that
req.queryis passed directly toUser.find()means an attacker can inject arbitrary MongoDB operators like{"role": {"$ne": null}}to dump all records.- Fix: Whitelist allowed query fields. Never pass raw
req.queryorreq.bodyto database operations. Use a validation library likejoiorzod:
- Fix: Whitelist allowed query fields. Never pass raw
- Broader hardening checklist:
- Add rate limiting per user/IP (
express-rate-limit+ Redis store for distributed) - Implement request payload size limits (
express.json({limit: '10kb'})) - Add CORS configuration restricted to your frontend domains
- Enable security headers via
helmet() - Log all access with correlation IDs for forensic analysis
- Add field-level projection so you never return
password,resetToken,__vfields - Implement API response pagination to prevent bulk data extraction
- Set up anomaly detection: alert when a single user requests >100 records/minute
- Add rate limiting per user/IP (
Scenario 5: Full-Stack Debugging — The Phantom 500 Error
Scenario 5: Full-Stack Debugging — The Phantom 500 Error
MongoServerError: connection pool was cleared at random intervals. Your APM (Datadog) shows p99 latency spikes correlating with the errors. Walk me through your debugging approach.What weak candidates say:
“I’d check the MongoDB connection string and maybe increase the connection pool size.”What strong candidates say:- Intermittent errors that do not reproduce locally are almost always infrastructure or connection management issues. The
connection pool was clearederror is a dead giveaway. This means Mongoose connection pool detected a problem with the MongoDB server and dropped all connections, forcing reconnection. Here is a systematic approach: - Step 1 - Check MongoDB health: Look at Atlas metrics (or your MongoDB monitoring) for the exact timestamps of the errors. Look for:
- Primary elections: If a replica set member steps down and a new primary is elected, all connections to the old primary are terminated. This causes exactly this error pattern.
- Network blips: Transient network issues between ECS and MongoDB. Check AWS CloudWatch for ENI errors, NAT gateway timeouts, or VPC flow log anomalies.
- Maintenance windows: Atlas performs maintenance that can trigger rolling restarts.
- Step 2 - Check Mongoose connection settings: The default Mongoose connection config is often too aggressive on timeouts:
- Step 3 - Implement retry logic at the application layer: MongoDB driver v4+ has built-in
retryWritesandretryReads, but Mongoose operations can still fail on pool clears. Wrap critical operations with retry logic:
- Step 4 - Add connection event monitoring:
- Step 5 - Check ECS task networking: If ECS tasks are cycling (deploys, autoscaling), new tasks might spike connection count. With 10 ECS tasks each opening 50 connections, that is 500 connections to MongoDB. Check if you are hitting Atlas connection limits.
- The real fix is usually a combination: resilient connection config, retry logic for transient errors, proper error handling in Express that returns 503 (Service Unavailable) with a
Retry-Afterheader instead of 500, and client-side retry logic in the React app for checkout (with idempotency keys to prevent double-charging).
Scenario 6: React State Management Meltdown
Scenario 6: React State Management Meltdown
- The problem is not Redux itself. It is that everything was put in Redux regardless of whether it belongs there. The fix is state colocation, not a wholesale migration. Here is the approach:
- Categorize state by scope and lifespan:
- Local UI state (modal open/close, hover, form inputs, accordions): Move to
useState/useReducerin the component that owns it. This is the biggest win. It eliminates 60-70% of the Redux noise immediately. - Shared client state (current user, theme, feature flags): Keep in a lightweight global store. Could stay in Redux or move to Zustand/Jotai.
- Server state (API data, loading, error states): Move to React Query or SWR. This alone eliminates the need for most async Redux middleware (
redux-thunk/redux-saga). - URL state (pagination, filters, search terms): Move to URL search params with
useSearchParams(). This makes the state shareable and bookmarkable.
- Local UI state (modal open/close, hover, form inputs, accordions): Move to
- Migration strategy (incremental, not big-bang):
- Week 1: Install React Query. Migrate the 3 most-used API data flows. Delete those reducers. Measure bundle size and render performance improvement.
- Week 2-3: Audit all 47 reducers. Tag each as “local”, “shared”, or “server”. Create a spreadsheet tracking migration status.
- Week 4-6: Migrate local state out of Redux, feature by feature. Each PR should remove one reducer and be independently deployable.
- Week 7-8: Evaluate what is left in Redux. If it is <5 reducers of genuinely shared client state, keep Redux (or switch to Zustand for simplicity).
- Form state specifically: For the form keystroke problem, use
react-hook-formwhich keeps form state in refs (not React state), so inputs do not trigger re-renders at all:
- What to avoid: Rewriting everything at once. Teams have spent 3 months on a “state management rewrite” that introduces as many bugs as it fixes. The incremental approach lets you ship improvements weekly and catch regressions early.
- Metrics to track: Time-to-interactive, bundle size (Redux + middleware can be 30-40KB), render count per user action (measure in React Profiler), and developer velocity (how fast can new features be built).
Scenario 7: Deploying MERN to Production — Everything Goes Wrong
Scenario 7: Deploying MERN to Production — Everything Goes Wrong
VITE_API_URL are undefined in the built frontend, and (4) CORS errors appear when the frontend calls the API. Diagnose all four issues.What weak candidates say:
“I’d check the console for errors. Maybe it’s a CORS configuration issue.”What strong candidates say:- These are four distinct issues that every MERN developer hits on their first real deployment. Addressing each:
- Issue 1 - Blank page on route refresh: This is the classic SPA routing problem. When the user is on
/dashboard/settingsand refreshes, Nginx tries to serve a file at that path, which does not exist. Nginx returns 404 instead of servingindex.htmland letting React Router handle it.- Fix — Add a catch-all in Nginx:
- Issue 2 - Slow API responses: On EC2, the Express server connects to MongoDB Atlas over the public internet instead of a local connection. Each query has an added 20-50ms network round trip. Also, if the EC2 instance is in
us-east-1but the Atlas cluster is inus-west-2, you are adding cross-region latency.- Fix: Enable VPC Peering between your AWS VPC and MongoDB Atlas. Ensure both are in the same region. Also enable connection pooling in Mongoose (default
maxPoolSizeis 100, which is fine) and add response compression:
- Fix: Enable VPC Peering between your AWS VPC and MongoDB Atlas. Ensure both are in the same region. Also enable connection pooling in Mongoose (default
- Issue 3 -
VITE_API_URLundefined: Vite environment variables are statically replaced at build time, not read at runtime. If the.env.productionfile was not present duringvite build, or if the variable does not start withVITE_, it will not be embedded in the bundle.- Fix: Ensure
.env.productionexists withVITE_API_URL=https://api.yourdomain.combefore runningvite build. Verify withgrep VITE_API_URL dist/assets/*.jsafter build. For dynamic runtime config, use awindow.__CONFIG__approach by injecting a script tag or serving a/config.jsonendpoint.
- Fix: Ensure
- Issue 4 - CORS errors: The frontend is served from
https://app.yourdomain.combut makes API calls tohttps://api.yourdomain.com(different subdomain = different origin). The Express API is not sending the right CORS headers.- Fix:
- Better approach: Configure Nginx to proxy
/api/*to the Express server so both frontend and API are served from the same origin, eliminating CORS entirely:
- Bonus production hardening: Enable Nginx rate limiting, add
pm2orsystemdto keep the Node process alive, setNODE_ENV=production(Express enables view caching, reduces verbose errors), configure SSL with Let’s Encrypt/Certbot, and add health check endpoints.
Scenario 8: MongoDB Data Modeling Disaster
Scenario 8: MongoDB Data Modeling Disaster
- This is a textbook example of embedded documents failing at scale. The fix is clear (move to a referenced model) but the migration strategy is the hard part. Covering both:
- Why the embedded model failed: MongoDB loads the entire document into memory for any operation on it. A 14MB post document means reading 50,000 comments just to display the post title. Every new comment rewrites the entire document. At 16MB, writes hard-fail.
- Target data model:
- The hybrid approach is key: embed the top 3 comments (for the feed preview) and reference the rest. This gives you the read performance of embedding for the common case (showing a post with a few comments) while handling scale for viral posts.
- Migration strategy (zero-ish downtime):
- Phase 1: Create the
commentscollection. Deploy new code that writes to both locations (embedded and referenced) using the dual-write pattern. - Phase 2: Run a background migration script that reads each post, extracts embedded comments, inserts them into the
commentscollection, and updates the post to keep onlytopComments+commentCount. Process in batches of 100 posts with a small delay between batches to avoid slamming the database. - Phase 3: Switch reads to the new
commentscollection. The feed still readstopCommentsfrom the post document. - Phase 4: Remove the old embedded comments array from post documents. Stop dual-writes.
- Phase 1: Create the
- Index the comments collection:
{postId: 1, createdAt: -1}for paginated comment loading. Add{postId: 1, likes: -1}if you support “top comments” sorting. - Pagination: Use cursor-based pagination on
createdAtrather than skip/limit. For a post with 50,000 comments,skip(49000)would scan and discard 49,000 documents:
- Keeping
commentCountin sync: Use MongoDB change streams or handle it in the application layer. Accept that the count might be slightly stale. Facebook and Instagram do this too.
$lookup (aggregation join) to fetch post + comments in one query, or do two separate queries from the application layer. What are the performance trade-offs?Follow-up: You need to support threaded/nested comments (replies to replies). How does this change your data model? What is the trade-off between materialized path, adjacency list, and nested set models?Follow-up: How would you handle the case where a viral post gets 500 new comments per second? At what point does even the referenced model struggle, and what do you reach for next?Scenario 9: Node.js Event Loop Starvation in an Express API
Scenario 9: Node.js Event Loop Starvation in an Express API
- Low CPU + high latency is the hallmark of event loop starvation. Something is blocking or monopolizing the event loop, preventing the REST handlers from executing. The CPU is low because most of the time is spent waiting, not computing. Here is the diagnosis:
- The SSE connection pattern is likely the culprit. Each SSE connection is a long-lived HTTP response. If the notification system is doing any synchronous work per SSE tick like iterating over all 500 connections in a tight loop, serializing data, or checking conditions, that blocks the event loop for the duration.
- Common antipattern to look for:
res.write() call is not truly async. If the TCP write buffer is full (slow clients), it can block. With 500 connections, even small per-client overhead accumulates.- Diagnosis with
--profandclinic.js:- Run
clinic doctor -- node server.jsunder load. It will show the event loop delay graph. Healthy is <10ms. This likely shows 500ms+ delays. - Use
process.hrtime()to measure event loop lag:
- Run
- Fix 1 - Batch SSE writes with
setImmediate:
- Fix 2 - Separate SSE into its own process: Use Node
clustermodule or a separate microservice for SSE connections. REST and SSE have fundamentally different resource profiles. REST is request-response (short bursts), SSE is long-lived (sustained connections). Mixing them on one event loop is asking for trouble. - Fix 3 - Use Redis Pub/Sub for notification distribution: Instead of one process managing all SSE connections, each Node process handles a subset. Notifications are published to a Redis channel, and each process subscribes and broadcasts to its own clients. This scales horizontally.
- The thread pool (
UV_THREADPOOL_SIZE) is a red herring here. That affects file I/O and DNS lookups, not HTTP connection handling. SSE operates on the main event loop.
Scenario 10: Full-Stack Performance Audit Under Business Pressure
Scenario 10: Full-Stack Performance Audit Under Business Pressure
- Two weeks with two developers means ruthless prioritization. The key is to identify the highest-impact, lowest-effort fixes first. Here is the plan, ordered by expected impact:
- Week 1 — The 80/20 Fixes (target: Lighthouse 60+, LCP <4s):
- Fix LCP (8.2s to target <4s): LCP is usually the hero image or the largest text block. Run Lighthouse and check what element is the LCP.
- If it is an image: Convert to WebP/AVIF, add
width/heightattributes to prevent layout shift, addfetchpriority="high"andloading="eager"to the LCP image specifically, serve via CDN (CloudFront/Cloudflare). This alone can cut 3-4 seconds. - If it is text rendered after a large JS bundle: That is a code-splitting problem. The main bundle is probably 2MB+ uncompressed. Implement route-based code splitting with
React.lazy()andSuspense. - Check if the font is render-blocking. Add
font-display: swapto@font-facedeclarations. Preload the primary font with a link preload tag withas="font"andcrossorigin.
- If it is an image: Convert to WebP/AVIF, add
- Fix CLS (0.45 to target <0.1): This is almost always caused by images without dimensions, dynamically injected ads/banners, or web fonts causing FOUT (Flash of Unstyled Text).
- Add explicit
widthandheightto everyimgtag. - Reserve space for dynamic content with CSS
min-heightoraspect-ratio. - Audit for any content that loads after initial paint and pushes things around.
- Add explicit
- Fix the 800ms API p95: Run
explain()on the top 5 slowest MongoDB queries (get these from Atlas Slow Query Analyzer or Mongoose debug mode). Add missing indexes. Enablecompressionmiddleware on Express. Add Redis caching for the product listing and category pages since they are read-heavy and cache-friendly.
- Fix LCP (8.2s to target <4s): LCP is usually the hero image or the largest text block. Run Lighthouse and check what element is the LCP.
- Week 2 — Deeper Optimizations (target: Lighthouse 70+, LCP <3s):
- Fix FID (380ms to target <100ms): High FID means the main thread is blocked during page load. The culprit is JavaScript, too much of it executing synchronously.
- Audit the bundle with
vite-plugin-visualizerorsource-map-explorer. Find the largest dependencies. Common offenders:moment.js(replace withdate-fnsordayjs), fulllodashimport (switch tolodash-eswith tree-shaking), analytics libraries loading synchronously. - Defer non-critical third-party scripts: analytics, chat widgets, social embeds. Use
asyncordeferattributes on script tags, or load them afterwindow.onload. - Move heavy computations to web workers if applicable.
- Audit the bundle with
- Enable HTTP/2 on Nginx: Multiplexed connections eliminate head-of-line blocking. This is a config change, not code.
- Add
stale-while-revalidatecaching headers for API responses that tolerate slight staleness (product listings, categories):
- Fix FID (380ms to target <100ms): High FID means the main thread is blocked during page load. The culprit is JavaScript, too much of it executing synchronously.
- Implement incremental static regeneration (if using Next.js) or pre-render critical pages to HTML at build time.
- What NOT to do in 2 weeks: Migrate to Next.js (too risky for a rewrite under pressure), implement a CDN from scratch (use Cloudflare with 5-minute setup), or “optimize all images” (focus only on above-the-fold and LCP images first).
- Measurement cadence: Run Lighthouse CI on every PR. Set up Real User Monitoring (RUM) with web-vitals library to track field data, not just lab data. Report daily to the CEO with the specific numbers and the revenue impact estimate.
Conclusion and Interview Tips
This comprehensive guide covers essential MERN Stack interview questions across all four technologies. The MERN stack combines MongoDB, Express.js, React, and Node.js to create powerful full-stack web applications.Key Interview Preparation Tips
- Master fundamentals before advanced topics — interviewers can tell immediately if you memorized answers vs truly understand concepts. Start with the event loop, reconciliation, and middleware pipeline before touching optimization techniques
- Build a full-stack project and deploy it — nothing replaces the experience of debugging a production CORS issue at 2am or figuring out why your MongoDB queries are slow with real data
- Understand how technologies integrate — the best MERN candidates can trace a request from React click to MongoDB write and back. Most candidates only know one layer deeply
- Focus on trade-offs, not just solutions — senior interviewers care more about why you chose X over Y than whether you know X exists
- Know your tools and metrics — be able to name specific tools: React DevTools Profiler for render performance,
explain()for MongoDB queries, Lighthouse for Core Web Vitals, Sentry for error tracking
During the Interview
- Ask clarifying questions before coding — “What scale are we talking about? How many concurrent users? What are the consistency requirements?” shows senior thinking
- Think aloud to show your problem-solving approach — interviewers cannot evaluate what they cannot hear. Verbalize trade-offs as you consider them
- Lead with the “why” before the “how” — “I would use Redis here because we need sub-millisecond lookups and the data has a natural TTL” is much stronger than “I would use Redis”
- Discuss trade-offs in your solutions — every technical decision has downsides. Acknowledging them shows engineering maturity
- Be honest about edges of your knowledge — “I have not used that in production, but my understanding is…” is far better than confidently giving a wrong answer
What Separates Good from Great Candidates
| Good Candidate | Great Candidate |
|---|---|
| Knows what React.memo does | Knows when React.memo hurts performance |
| Can write a JWT auth flow | Can explain why JWTs in localStorage are insecure |
| Understands MongoDB indexing | Can design the right compound index for a given query pattern |
| Knows the event loop phases | Can explain why setImmediate and process.nextTick have different use cases |
| Lists performance optimization techniques | Measures first, optimizes second, and can explain the specific bottleneck they fixed |