Skip to content

Hard MERN Stack Interview Questions

42 hard questions with answers. Design, trade-off and deep-dive questions asked for senior roles and at top product companies.

Topics in the hard set

React Hooks, State and Performance: 9 Authentication and Security: 7 Modern JavaScript (ES6+): 6 MongoDB and Mongoose: 6 Testing, Deployment and DevOps: 6 Node.js Core: 5 Express.js and REST APIs: 3
Solved — hardStart 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

How does prototypal inheritance work, and how does class syntax relate to it?

Save to ▾
Show answer
Every object has an internal link to a prototype object. When a property is missing, JavaScript walks this prototype chain until it finds it or reaches null. Class syntax is mostly sugar over this model: methods defined in a class live on Constructor.prototype, and extends sets up the chain between prototypes. Classes add stricter behaviour, such as requiring new and supporting private fields with the # prefix.
02

You need to call an external API 500 times but it allows only 5 concurrent requests. How would you implement this?

Save to ▾
Show answer
Use a concurrency-limited queue. Keep a pool of five workers; each worker picks the next item from a shared index, awaits the request, and loops until the list is exhausted. Run the workers with Promise.all. Libraries such as p-limit do this neatly. Add retries with backoff for 429 responses and collect failures separately instead of failing the whole batch, so one bad item does not stop the job.
03

A single-page app gets slower the longer a user keeps it open. What JavaScript memory leaks would you check for?

Save to ▾
Show answer
Look for event listeners, intervals and subscriptions added without cleanup, closures holding large objects, detached DOM nodes still referenced in variables, growing caches or global arrays, and WebSocket handlers registered on every render. In React, these usually come from useEffect without a cleanup function. Use the Chrome DevTools Memory tab to take heap snapshots before and after an action and compare retained objects.
04

Implement your own version of Promise.all.

Save to ▾
Show answer
It must keep result order and reject on the first failure. function promiseAll(items) { return new Promise((resolve, reject) => { const results = []; let done = 0; if (items.length === 0) return resolve(results); items.forEach((item, i) => { Promise.resolve(item).then((value) => { results[i] = value; done += 1; if (done === items.length) resolve(results); }, reject); }); }); } Promise.resolve handles plain values mixed with Promises.
05

Write a deep clone function that handles nested objects, arrays and dates.

Save to ▾
Show answer
In production prefer structuredClone, but interviewers often ask for a manual version. function deepClone(value, seen = new WeakMap()) { if (value === null || typeof value !== 'object') return value; if (value instanceof Date) return new Date(value); if (seen.has(value)) return seen.get(value); const copy = Array.isArray(value) ? [] : {}; seen.set(value, copy); for (const key of Object.keys(value)) copy[key] = deepClone(value[key], seen); return copy; } The WeakMap prevents infinite loops on circular references.
06

Write an async helper that retries a failing call with exponential backoff.

Save to ▾
Show answer
Wait longer after each failure and give up after a limit. async function retry(fn, retries = 3, base = 500) { for (let attempt = 0; ; attempt++) { try { return await fn(); } catch (err) { if (attempt >= retries) throw err; const wait = base * 2 ** attempt + Math.random() * 100; await new Promise((r) => setTimeout(r, wait)); } } } The random jitter stops many clients from retrying at the same moment.
07

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.
08

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.
09

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.
10

How should errors be handled in a Node.js application?

Save to ▾
Show answer
Separate operational errors, like a failed DB connection or invalid input, which you handle and respond to, from programmer errors, like undefined property access, which signal bugs. Use try/catch with async/await, always handle Promise rejections and stream error events. Listen to uncaughtException and unhandledRejection only to log and exit cleanly, then let a process manager restart the app, because state may be corrupted.
11

Memory usage of a production Node.js service keeps rising until it restarts. How do you investigate?

Save to ▾
Show answer
First confirm the trend with monitoring of heapUsed and RSS. Reproduce in staging under load, then take heap snapshots with --inspect and Chrome DevTools, or use --heapsnapshot-signal, and compare snapshots to find growing object types and their retainers. Common causes are unbounded in-memory caches, listeners added per request, global arrays and unclosed timers. Fix the root cause, add a bounded LRU cache, and set alerts.
12

Compare offset-based and cursor-based pagination for an API.

Save to ▾
Show answer
Offset pagination uses page and limit, translating to skip and limit in MongoDB. It is simple and allows jumping to page 10, but gets slower on large collections and can skip or repeat items when data changes. Cursor pagination returns items after a stable sorted value, such as the last _id or createdAt, using an indexed range query. It stays fast and consistent, ideal for infinite scroll feeds.
13

Your Express API slows down badly during peak traffic. How do you find and fix the bottleneck?

Save to ▾
Show answer
Measure first: add request timing logs or APM tracing to find slow endpoints, then check whether time goes to the database, external calls or CPU. Use explain() for slow queries and add indexes, cache hot read endpoints in Redis, enable compression, paginate large responses and remove synchronous code from handlers. Then scale horizontally with multiple instances behind a load balancer and load test with k6 or autocannon.
14

Design a payment endpoint so that client retries never charge a user twice.

Save to ▾
Show answer
Make the endpoint idempotent with an idempotency key. The client generates a unique key per checkout and sends it in a header. The server stores the key with a unique index along with the request hash and the result. If the same key arrives again, return the stored response instead of reprocessing. Use a status field for in-progress requests and verify final status through the payment gateway webhook.
15

Explain indexes in MongoDB, compound indexes and the ESR rule.

Save to ▾
Show answer
An index is a sorted structure that lets queries avoid scanning every document. A compound index covers multiple fields in order, like { userId: 1, createdAt: -1 }, and supports queries on its leading prefix. The ESR guideline says order fields as Equality first, then Sort, then Range. Indexes speed reads but slow writes and use memory, so create them for real query patterns and drop unused ones.
16

What is the difference between Mongoose populate and the $lookup aggregation stage?

Save to ▾
Show answer
populate is a Mongoose feature that replaces ObjectId references with documents by running extra queries from the application. It is convenient and readable but issues additional round trips. $lookup is a server-side join inside the aggregation pipeline, which can also filter, project and combine with grouping in one query. Use populate for simple CRUD views and $lookup for reports or when you need to filter on joined data.
17

How do transactions work in MongoDB and when do you need them?

Save to ▾
Show answer
Single-document writes are already atomic, so good embedded design avoids most transactions. Multi-document ACID transactions are available on replica sets and sharded clusters, not on a standalone server. In Mongoose, start a session, call session.withTransaction() and pass the session to every operation. Use them for cases like transferring wallet balance between two users. They add latency and should stay short, ideally under one second.
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

You stored all comments inside each blog post document and some posts are now very large. How would you redesign it?

Save to ▾
Show answer
An unbounded embedded array risks hitting the 16 MB document limit and makes every post read and update heavy. Move comments to their own collection with a postId field and an index on { postId: 1, createdAt: -1 }, then paginate. Optionally keep a small embedded array of the latest few comments plus a commentCount on the post for fast first render. This is the subset pattern.
20

You need to rename a field and change its type across 20 million documents in production. How do you do it safely?

Save to ▾
Show answer
Avoid a single big blocking update. Make the application read both old and new formats first, then run a batched migration script that processes documents in chunks by _id range with bulkWrite, throttled and resumable, or an updateMany with an aggregation pipeline during low traffic. Track progress, verify counts, back up first, then remove the old-field handling in a later release.
21

What is the difference between server state and client state, and why use a library like TanStack Query?

Save to ▾
Show answer
Client state is UI data owned by the browser, like modal open or selected tab. Server state lives in the database, can be changed by others, and is only a cached copy in the browser. TanStack Query manages server state with caching, deduplication, background refetching, retries, pagination and mutations with cache invalidation. This removes much hand-written useEffect fetching and stops global stores from holding stale API data.
22

What are the key new features in React 19?

Save to ▾
Show answer
React 19 added Actions, letting async functions handle form submissions with pending and error state; hooks such as useActionState, useFormStatus and useOptimistic; the use API to read Promises and context; ref as a normal prop instead of forwardRef; support for document metadata tags like title inside components; and stable Server Components. The separate React Compiler can automatically memoize components, reducing manual useMemo and useCallback.
23

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.
24

Typing in a filter box over a list of 10,000 rows feels laggy. How do you fix it?

Save to ▾
Show answer
First profile to confirm what re-renders. Then virtualise the list with a library like TanStack Virtual or react-window so only visible rows render. Memoise row components and keep the filtered list in useMemo. Use useDeferredValue or startTransition so the input stays responsive while the list update is treated as low priority. For very large data, filter on the server with pagination and an index.
25

A setInterval inside useEffect always logs the initial count value instead of the updated one. Why, and how do you fix it?

Save to ▾
Show answer
This is a stale closure: the effect ran once with an empty dependency array, so its callback captured count from the first render forever. Fixes include using the functional updater setCount((c) => c + 1) so you do not need to read count, adding count to the dependencies with proper cleanup, or storing the latest value in a ref that the interval reads.
26

Your React production bundle is 3 MB and the first load is slow on mobile. What steps do you take?

Save to ▾
Show answer
Analyse the bundle to find the largest modules. Split code by route with React.lazy, lazy load heavy libraries like charts and editors, replace large dependencies with lighter ones, import only needed functions so tree shaking works, and remove unused polyfills. Compress images, serve assets from a CDN with Brotli and long cache headers, and consider SSR or static rendering for landing pages. Track Core Web Vitals after each change.
27

Fast typing in a search box sometimes shows results for an older query. What causes this and how do you fix it?

Save to ▾
Show answer
It is a race condition: responses arrive out of order, so a slow earlier request overwrites results of a newer one. In the effect, use an AbortController and abort the previous request in the cleanup, or keep an ignore flag set to true during cleanup and skip setState when it is set. Debouncing reduces requests, and TanStack Query handles this automatically by keying results to the query.
28

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;
29

Tell me about a time you led a technical migration in a frontend codebase.

Save to ▾
Show answer
Situation: our React app still used Create React App and class components, and builds took several minutes. Task: as the senior developer I proposed moving to Vite and hooks. Action: I wrote a migration plan, moved the build tool first behind a feature branch, fixed environment variable names, then converted components gradually with tests. Result: dev server start went from 40 seconds to 2, builds became much faster, and the team adopted hooks patterns.
30

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.
31

What is CSRF and how do SameSite cookies help prevent it?

Save to ▾
Show answer
Cross-site request forgery tricks a logged-in user's browser into sending a request to your site from a malicious page, and the browser attaches the user's cookies automatically. SameSite=Lax blocks cookies on cross-site POST requests and SameSite=Strict blocks them on all cross-site requests, which stops most CSRF. For extra safety use CSRF tokens on state-changing routes, never change state with GET, and verify the Origin header.
32

How does Sign in with Google work using OAuth 2.0 and OpenID Connect?

Save to ▾
Show answer
The app redirects the user to Google's authorization endpoint with client id, redirect URI, scopes like openid email and a state value. After consent, Google redirects back with an authorization code, and the backend exchanges it for an ID token and access token. With OpenID Connect, the ID token is a signed JWT whose signature, audience and expiry you verify, then you create your own session. PKCE protects the code exchange.
33

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.
34

Users can upload profile pictures and documents. What security measures do you put in place?

Save to ▾
Show answer
Enforce size limits and an allowlist of types, checking magic bytes rather than trusting the extension or Content-Type. Rename files with random ids, store them in object storage outside the web root, and serve them from a separate domain or with Content-Disposition attachment for documents. Strip image metadata, scan for malware where needed, use pre-signed URLs with expiry for private files, and never execute or include uploaded files.
35

Write an Axios interceptor in React that refreshes the access token on a 401 response.

Save to ▾
Show answer
Retry the failed request once after refreshing. const api = axios.create({ baseURL: '/api', withCredentials: true }); let accessToken = null; api.interceptors.request.use((config) => { if (accessToken) config.headers.Authorization = `Bearer ${accessToken}`; return config; }); api.interceptors.response.use((res) => res, async (error) => { const original = error.config; if (error.response?.status === 401 && !original._retry) { original._retry = true; const { data } = await api.post('/auth/refresh'); accessToken = data.accessToken; return api(original); } return Promise.reject(error); }); In production, queue parallel requests behind one shared refresh promise.
36

How do you handle pressure to ship quickly when you think a feature is not secure yet?

Save to ▾
Show answer
Situation: a product manager wanted a file-sharing feature live by Friday, but uploads had no type or access checks. Task: protect users without blocking the launch. Action: I explained the risk in business terms, proposed shipping with private pre-signed links and type limits first, and deferring sharing features to the next sprint. Result: we launched on time with the risky part removed, and the full feature followed a week later.
37

What logging and monitoring should a production MERN app have?

Save to ▾
Show answer
Use structured JSON logs with a fast logger like pino, including a request id to trace a request across services, and ship logs to a central system. Track metrics such as request rate, error rate, latency percentiles, event-loop lag, memory and database performance, with dashboards and alerts. Add error tracking for both React and Node, uptime checks and distributed tracing with OpenTelemetry for microservices.
38

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.
39

Your MERN product is expected to grow from 1,000 to 1 lakh daily users. How would you prepare the architecture?

Save to ▾
Show answer
Make the API stateless and run multiple instances behind a load balancer with autoscaling. Serve the React build and media from a CDN. Move sessions, rate limits and caches to Redis. Optimise MongoDB with proper indexes, a replica set, read preferences for analytics and plan sharding if data grows. Push slow work to queues, add monitoring and alerts, and load test before launch to find limits.
40

How would you achieve zero-downtime deployments for an Express API?

Save to ▾
Show answer
Run at least two instances behind a load balancer and use rolling or blue-green deployments: start new instances, wait for health and readiness checks to pass, shift traffic, then drain and stop old instances with graceful shutdown. PM2 reload or Kubernetes rolling updates handle this. Keep database migrations backward compatible, adding fields before code uses them, so old and new versions can run together.
41

Your chat feature uses Socket.IO and must now run on multiple Node instances. What changes are needed?

Save to ▾
Show answer
Each instance only knows its own connected sockets, so a message emitted on one would not reach users on another. Add the Socket.IO Redis adapter, or a similar pub/sub adapter, so events are broadcast across instances. Enable sticky sessions at the load balancer if HTTP long-polling fallback is used. Store presence and room membership in Redis, persist messages in MongoDB, and authenticate sockets with tokens during the handshake.
42

Write a GitHub Actions workflow that tests and builds a MERN project on every pull request.

Save to ▾
Show answer
Run server and client jobs in parallel. name: ci on: pull_request: push: branches: [main] jobs: server: runs-on: ubuntu-latest defaults: run: working-directory: server steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm cache-dependency-path: server/package-lock.json - run: npm ci - run: npm run lint - run: npm test client: runs-on: ubuntu-latest defaults: run: working-directory: client steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm run build

Frequently asked questions

Who should practise hard MERN Stack questions?

Candidates targeting senior roles or top product companies, and anyone who finds the medium set easy.

How should I practise these?

Answer each question aloud before opening the answer, mark it as solved only when your answer matches the key idea, and save misses to Last-Minute Revision.

Other difficulty levels