Skip to content

MERN Stack Technical Round Questions & Answers

40 questions for the technical round stage — 4 easy, 27 medium, 9 hard.

About the technical round round

The technical round is the core of a MERN Stack interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

Tips for the technical round round

  • Say your approach out loud before you start writing.
  • After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question.
  • Pick one project you can explain end to end, including what you would do differently.
  • If you get stuck, explain what you would try next instead of going silent.
Solved — technical roundStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

Create an AuthContext with a provider and a custom hook.

Save to ▾
Show answer
A provider plus a safe hook: const AuthContext = createContext(null); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const login = async (creds) => { const res = await api.post('/auth/login', creds); setUser(res.data.user); }; const logout = () => setUser(null); const value = useMemo(() => ({ user, login, logout }), [user]); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used inside AuthProvider'); return ctx; }
02

What are React fragments and portals?

Save to ▾
Show answer
A fragment, written <>...</> or <Fragment key={id}>, groups children without adding an extra DOM element, which keeps tables and flex layouts valid. A portal, created with createPortal(child, domNode), renders children into a different DOM node such as document.body while keeping them in the same React tree, so context and event bubbling still work. Portals are used for modals, tooltips and toasts that must escape overflow hidden containers.
03

What does React.memo do and when is it useful?

Save to ▾
Show answer
React.memo wraps a component so it skips re-rendering when its props are shallowly equal to the previous props. It helps with expensive components that re-render often because the parent re-renders, such as rows in a large table. It fails if you pass new object, array or function props every render, so pair it with useMemo and useCallback. Measure with React DevTools Profiler before adding it.
04

Write an aggregation that counts orders and sums revenue per status.

Save to ▾
Show answer
Group by status and sort by revenue. db.orders.aggregate([ { $match: { createdAt: { $gte: ISODate('2026-01-01') } } }, { $group: { _id: '$status', orders: { $sum: 1 }, revenue: { $sum: '$amount' } } }, { $sort: { revenue: -1 } }, { $project: { status: '$_id', orders: 1, revenue: 1, _id: 0 } } ]) In Mongoose the same array goes into Order.aggregate([...]).
05

Your app needs user, editor and admin roles with different permissions. How would you implement it?

Save to ▾
Show answer
Store a role, or a list of permissions, on the user document and include it in the token or load it in the auth middleware. Write an authorize middleware that checks allowed roles or permissions per route, for example authorize('admin'). Add ownership checks in services, such as an editor editing only their own articles. In React, hide buttons by role for user experience, but always enforce rules on the server.
06

An error in one dashboard widget crashes the entire React page. How do you contain it?

Save to ▾
Show answer
Wrap risky sections in error boundaries. An error boundary catches rendering errors in its subtree and shows fallback UI instead of unmounting the whole app. React still requires a class with getDerivedStateFromError and componentDidCatch, or you can use the react-error-boundary package. Place boundaries around each widget and the route level, log errors to monitoring, and offer a retry. They do not catch errors in event handlers or async code.
07

Implement page-based pagination for a products API with Mongoose.

Save to ▾
Show answer
Return items plus metadata. async function listProducts(req, res) { const page = Math.max(1, Number(req.query.page) || 1); const limit = Math.min(50, Number(req.query.limit) || 10); const [items, total] = await Promise.all([ Product.find().sort({ createdAt: -1 }) .skip((page - 1) * limit).limit(limit).lean(), Product.countDocuments() ]); res.json({ items, page, totalPages: Math.ceil(total / limit) }); } Capping limit prevents clients from requesting huge pages.
08

What are BSON and ObjectId in MongoDB?

Save to ▾
Show answer
BSON is the binary format MongoDB uses to store documents. It extends JSON with types like Date, ObjectId, Decimal128, binary data and 32 or 64-bit integers, and is fast to traverse. ObjectId is the default 12-byte _id value made of a 4-byte timestamp, a 5-byte random value and a 3-byte counter. Because it starts with a timestamp, ObjectIds roughly sort by creation time.
09

What is the aggregation pipeline and what are its common stages?

Save to ▾
Show answer
The aggregation pipeline processes documents through a sequence of stages, each transforming the stream. Common stages are $match to filter, $group to aggregate with operators like $sum and $avg, $project or $addFields to shape fields, $sort, $limit, $skip, $lookup to join another collection, $unwind to flatten arrays and $facet for multiple outputs. Put $match and $sort early so they can use indexes and reduce the data volume.
10

Why do applications use both access tokens and refresh tokens?

Save to ▾
Show answer
The access token is short-lived, for example 15 minutes, and is sent with every API request, so if stolen it is useful only briefly. The refresh token is long-lived, stored more securely in an httpOnly cookie, and is used only at a refresh endpoint to get new access tokens. Refresh tokens should be stored server-side or hashed so they can be revoked, and rotated on every use to detect theft.
11

Compare session-based authentication with JWT-based authentication.

Save to ▾
Show answer
With sessions, the server stores session data in a store such as Redis and the browser holds only a session id cookie; revoking is easy, but every request needs a lookup. With JWTs, the token itself carries signed claims, so servers can verify it without a lookup, which suits microservices and mobile clients. JWTs are harder to revoke before expiry, so keep them short-lived and pair them with refresh tokens.
12

Set a refresh token in a secure httpOnly cookie after login.

Save to ▾
Show answer
Cookie flags carry most of the security. const refreshToken = jwt.sign({ sub: user.id }, process.env.REFRESH_SECRET, { expiresIn: '7d' }); const accessToken = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '15m' }); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'strict', path: '/api/auth/refresh', maxAge: 7 * 24 * 60 * 60 * 1000 }); res.json({ accessToken }); Read it later with the cookie-parser middleware via req.cookies.refreshToken.
13

When a user logs out or changes password, how do you make sure their existing JWTs stop working?

Save to ▾
Show answer
Stateless JWTs stay valid until expiry, so keep access tokens short-lived and revoke refresh tokens by deleting or flagging them in the database. For immediate invalidation, store a tokenVersion or passwordChangedAt on the user and include it in the token; the auth middleware rejects tokens with an older version. Alternatively keep a Redis denylist of token ids until they expire. Clear the refresh cookie on logout.
14

Should you store a JWT in localStorage or in an httpOnly cookie?

Save to ▾
Show answer
localStorage is readable by any JavaScript on the page, so an XSS bug can steal the token. An httpOnly, Secure cookie cannot be read by JavaScript, which protects against token theft through XSS, but cookies are sent automatically, so you must defend against CSRF with SameSite=Lax or Strict and CSRF tokens where needed. A common pattern keeps the refresh token in an httpOnly cookie and the access token in memory.
15

Your script crashes with out of memory while reading a 5 GB log file. How would you rewrite it?

Save to ▾
Show answer
fs.readFile loads the whole file into memory, which exceeds the heap. Use a stream instead: fs.createReadStream with readline lets you process one line at a time with for await, keeping memory flat. If you need to transform and write output, connect read, transform and write streams with pipeline so backpressure and errors are handled. Increasing --max-old-space-size only hides the real problem.
16

Write an asyncHandler wrapper for Express 4 route handlers.

Save to ▾
Show answer
It catches rejected Promises and forwards them to error middleware. const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); router.get('/:id', asyncHandler(async (req, res) => { const user = await User.findById(req.params.id); if (!user) return res.status(404).json({ message: 'Not found' }); res.json(user); })); Express 5 does this automatically, but many existing codebases still use this pattern.
17

Write a Redux Toolkit slice that fetches products with createAsyncThunk.

Save to ▾
Show answer
The slice handles pending, fulfilled and rejected states. export const fetchProducts = createAsyncThunk('products/fetch', async () => { const res = await fetch('/api/products'); return res.json(); }); const productsSlice = createSlice({ name: 'products', initialState: { items: [], status: 'idle', error: null }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchProducts.pending, (s) => { s.status = 'loading'; }) .addCase(fetchProducts.fulfilled, (s, a) => { s.status = 'done'; s.items = a.payload; }) .addCase(fetchProducts.rejected, (s, a) => { s.status = 'failed'; s.error = a.error.message; }); } }); export default productsSlice.reducer;
18

What are replica sets and sharding in MongoDB, and how do you choose a shard key?

Save to ▾
Show answer
A replica set is a primary plus secondaries holding copies of data, giving automatic failover and optional read scaling. Sharding splits a collection across multiple shards for horizontal scaling of data size and write throughput. A good shard key has high cardinality, evenly distributed writes and matches common queries so they target one shard. Monotonic keys like createdAt create hot spots unless hashed.
19

Write a central error-handling middleware with a custom AppError class.

Save to ▾
Show answer
One handler keeps responses consistent. class AppError extends Error { constructor(message, statusCode = 500) { super(message); this.statusCode = statusCode; this.isOperational = true; } } function errorHandler(err, req, res, next) { if (err.code === 11000) err = new AppError('Duplicate value', 409); const status = err.statusCode || 500; res.status(status).json({ message: err.isOperational ? err.message : 'Something went wrong' }); } app.use(errorHandler); // register last
20

Write a register endpoint that hashes the password with bcrypt.

Save to ▾
Show answer
Validate, check duplicates and hash before saving. import bcrypt from 'bcrypt'; router.post('/register', async (req, res) => { const { name, email, password } = req.body; if (!email || !password || password.length < 8) { return res.status(400).json({ message: 'Invalid input' }); } const exists = await User.findOne({ email: email.toLowerCase() }); if (exists) return res.status(409).json({ message: 'Email already registered' }); const hash = await bcrypt.hash(password, 12); const user = await User.create({ name, email, password: hash }); res.status(201).json({ id: user._id, name: user.name }); }); A unique index on email still guards against races.
21

Write an Express middleware that verifies a JWT from the Authorization header.

Save to ▾
Show answer
Reject missing or invalid tokens with 401. import jwt from 'jsonwebtoken'; export function requireAuth(req, res, next) { const header = req.headers.authorization || ''; const token = header.startsWith('Bearer ') ? header.slice(7) : null; if (!token) return res.status(401).json({ message: 'Token missing' }); try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); } catch { res.status(401).json({ message: 'Invalid or expired token' }); } } router.get('/me', requireAuth, getProfile);
22

Write a Supertest integration test for a POST /api/users endpoint.

Save to ▾
Show answer
Import the app, not the running server. import request from 'supertest'; import app from '../src/app.js'; describe('POST /api/users', () => { it('creates a user', async () => { const res = await request(app) .post('/api/users') .send({ name: 'Aman', email: 'aman@test.com' }) .expect(201); expect(res.body.email).toBe('aman@test.com'); }); it('rejects invalid email', async () => { await request(app).post('/api/users').send({ name: 'A', email: 'x' }).expect(400); }); }); Connect to mongodb-memory-server in a beforeAll hook.
23

Fetch a list of users from an API inside useEffect with proper cleanup.

Save to ▾
Show answer
AbortController cancels the request if the component unmounts. function Users() { const [users, setUsers] = useState([]); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); fetch('/api/users', { signal: controller.signal }) .then((r) => r.json()) .then(setUsers) .catch((e) => { if (e.name !== 'AbortError') setError(e); }); return () => controller.abort(); }, []); if (error) return <p>Failed to load</p>; return <ul>{users.map((u) => <li key={u._id}>{u.name}</li>)}</ul>; }
24

What is libuv and which operations use the Node.js thread pool?

Save to ▾
Show answer
libuv is the C library that provides Node's event loop and asynchronous I/O. Network I/O uses non-blocking OS mechanisms like epoll or IOCP, so it does not use the pool. The thread pool, four threads by default, handles file system calls, DNS lookup, zlib compression and crypto functions like pbkdf2 and scrypt. Heavy bcrypt or crypto load can saturate it; UV_THREADPOOL_SIZE can increase it.
25

Write a role-based authorization middleware for Express.

Save to ▾
Show answer
A small factory function keeps routes readable. export const authorize = (...roles) => (req, res, next) => { if (!req.user) return res.status(401).json({ message: 'Login required' }); if (!roles.includes(req.user.role)) { return res.status(403).json({ message: 'Forbidden' }); } next(); }; router.delete('/products/:id', requireAuth, authorize('admin'), deleteProduct); router.patch('/articles/:id', requireAuth, authorize('admin', 'editor'), updateArticle); It must run after the authentication middleware that sets req.user.
26

Explain the phases of the Node.js event loop.

Save to ▾
Show answer
Each loop iteration goes through phases: timers runs expired setTimeout and setInterval callbacks; pending callbacks runs some deferred system callbacks; poll waits for and runs I/O callbacks; check runs setImmediate callbacks; close callbacks handles events like socket close. Between every callback, Node drains process.nextTick callbacks and then Promise microtasks. Knowing this explains why setImmediate inside an I/O callback always runs before a setTimeout of 0.
27

Compare worker_threads, cluster and child_process in Node.js.

Save to ▾
Show answer
cluster forks multiple Node processes that share one server port, used to use all CPU cores for an HTTP app, though PM2 or containers often do this instead. child_process runs a separate program or script, for example ffmpeg, communicating through stdio or IPC. worker_threads run JavaScript in parallel threads inside one process and can share memory through SharedArrayBuffer, best for CPU-heavy tasks like image processing or parsing.
28

Explain CSR, SSR, hydration and React Server Components.

Save to ▾
Show answer
With client-side rendering, the browser downloads JavaScript and builds the page, which is slower for first paint and SEO. Server-side rendering sends ready HTML, then hydration attaches React event handlers in the browser. React Server Components run only on the server, can query data directly, and send no JavaScript for themselves, while client components marked use client handle interactivity. Frameworks like Next.js implement these patterns.
29

How would you structure the folders of a production Express API?

Save to ▾
Show answer
A layered structure keeps responsibilities clear: src/routes for route definitions, src/controllers for request and response handling, src/services for business logic, src/models for Mongoose schemas, src/middlewares for auth, validation and errors, src/config for environment and database setup, src/utils for helpers, and tests alongside or in a tests folder. Keep app.js, which builds the app, separate from server.js, which listens, so tests can import the app.
30

What practical steps do you take to sanitise and validate input in an Express and MongoDB API?

Save to ▾
Show answer
Validate every request body, params and query against a schema with zod or Joi, rejecting unknown fields and wrong types. Apply body size limits with express.json({ limit: '100kb' }). Strip MongoDB operator keys from input with a small sanitising middleware or library, and keep Mongoose strict mode on. Escape or sanitise HTML with DOMPurify before storing rich text, and validate ObjectIds before querying.
31

What does lean() do in Mongoose and when should you use it?

Save to ▾
Show answer
By default Mongoose queries return full documents with change tracking, getters, virtuals and methods like save. Calling .lean() returns plain JavaScript objects, which uses much less memory and is noticeably faster. Use it for read-only endpoints such as lists and API responses. Skip it when you need to modify and save the document, or depend on virtuals and getters unless you add a lean plugin.
32

Write a function to flatten a nested array without using Array.prototype.flat.

Save to ▾
Show answer
Recursion with reduce is the cleanest approach. function flatten(arr) { return arr.reduce((acc, item) => Array.isArray(item) ? acc.concat(flatten(item)) : acc.concat(item), []); } flatten([1, [2, [3, [4]]], 5]); // [1, 2, 3, 4, 5] For very deep arrays, an iterative version with an explicit stack avoids hitting the recursion limit.
33

How would you use Redis caching in a MERN backend and what are the pitfalls?

Save to ▾
Show answer
Use the cache-aside pattern: check Redis for a key, return it on a hit, otherwise query MongoDB, store the result with a TTL and return it. Good targets are expensive, frequently read data like product listings. Pitfalls include stale data, so invalidate or update keys on writes; cache stampede when many requests miss at once, solved with locking or early refresh; and memory limits, managed with eviction policies.
34

What is the difference between a shallow copy and a deep copy?

Save to ▾
Show answer
A shallow copy duplicates only the top level, so nested objects are still shared references. Spread syntax and Object.assign make shallow copies. A deep copy duplicates every nested level. structuredClone(obj) is the built-in way in modern browsers and Node.js and supports Dates, Maps and Sets, while JSON.parse(JSON.stringify(obj)) loses functions, undefined and Dates. In React state updates, shallow copies of each changed level are usually enough.
35

What is a custom hook and what rules must hooks follow?

Save to ▾
Show answer
A custom hook is a function whose name starts with use and that calls other hooks to package reusable stateful logic, such as useFetch, useDebounce or useAuth. Each component using it gets its own state. Hooks must be called at the top level of a component or another hook, never inside conditions, loops or nested functions, so React can match state by call order. The eslint-plugin-react-hooks rules enforce this.
36

How does async/await improve on Promise chains, and how do you handle errors with it?

Save to ▾
Show answer
async/await is syntax over Promises that lets asynchronous code read top to bottom like synchronous code. An async function always returns a Promise, and await pauses that function until the Promise settles. Errors are handled with try/catch/finally instead of chained catch calls. A common mistake is awaiting independent calls one after another; use Promise.all to run them in parallel when they do not depend on each other.
37

What is the difference between useMemo and useCallback?

Save to ▾
Show answer
useMemo caches the result of a calculation, recomputing only when dependencies change, for example a filtered and sorted list. useCallback caches a function reference, so useCallback(fn, deps) equals useMemo(() => fn, deps). They help when passing values or callbacks to memoized children or as effect dependencies. They have their own cost, so do not wrap everything; the React Compiler can now add this memoization automatically.
38

Implement a shopping cart with useReducer.

Save to ▾
Show answer
A pure reducer keeps cart logic in one place. function cartReducer(state, action) { switch (action.type) { case 'add': { const found = state.find((i) => i.id === action.item.id); return found ? state.map((i) => i.id === action.item.id ? { ...i, qty: i.qty + 1 } : i) : [...state, { ...action.item, qty: 1 }]; } case 'remove': return state.filter((i) => i.id !== action.id); case 'clear': return []; default: return state; } } const [cart, dispatch] = useReducer(cartReducer, []); dispatch({ type: 'add', item: product });
39

What is the Context API and when should you avoid it?

Save to ▾
Show answer
Context passes data through the component tree without prop drilling. You create it with createContext, provide a value with a Provider and read it with useContext, or the newer use API. It suits low-frequency global data like theme, language and logged-in user. Avoid it for frequently changing large state, because every consumer re-renders when the value changes; split contexts or use Zustand or Redux Toolkit instead.
40

Write a counter component using useState.

Save to ▾
Show answer
Use the functional updater when the new value depends on the old one. import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount((c) => c + 1)}>+</button> <button onClick={() => setCount((c) => c - 1)}>-</button> <button onClick={() => setCount(0)}>Reset</button> </div> ); }

Frequently asked questions

What happens in the MERN Stack technical round round?

The technical round is the core of a MERN Stack interview: concept questions with follow-ups, a live hands-on task and a deep dive into one of your projects. Interviewers check depth, not just definitions.

How do I clear the technical round round?

Say your approach out loud before you start writing. After a correct answer, expect 'why?' and 'what if?' follow-ups; go one level deeper than the question. Pick one project you can explain end to end, including what you would do differently.

Which round comes next?

A typical order is online assessment, telephonic screening, one or two technical rounds, a managerial round and HR. Use the other round pages to practise each stage.