Skip to content

MERN Stack Interview Questions for 2 Years Experience

40 questions with answers for candidates with 2 years of experience — 4 easy, 32 medium, 4 hard.

What interviewers expect: 2 years experience

At two years, questions move from what to why. Interviewers probe re-render issues, useEffect pitfalls, state management choices, MongoDB indexing and schema design, pagination, and secure token handling. You should explain trade-offs between approaches, describe a performance problem you solved, and show that you write tests. Some companies add a small take-home assignment covering both frontend and backend.

This set is weighted towards React Hooks, State and Performance (8), Authentication and Security (7), Express.js and REST APIs (6), Node.js Core (5).

React performance Schema design and indexes Auth flows Testing
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

What is the difference between controlled and uncontrolled components?

Save to ▾
Show answer
In a controlled component, form input values live in React state and change through onChange, so React is the single source of truth, which makes validation and conditional UI easy. In an uncontrolled component, the DOM holds the value and you read it with a ref or FormData when needed. Uncontrolled inputs mean fewer re-renders and suit simple forms and file inputs; libraries like React Hook Form use this approach.
02

A component calls setState inside useEffect and the app goes into an infinite render loop. Why?

Save to ▾
Show answer
The effect updates state, which re-renders, which runs the effect again. This happens when the dependency array is missing, or when a dependency is a new object or array created every render, or when the effect sets the same state it depends on. Add correct dependencies, move object creation inside the effect or memoize it, and ask whether the value can be calculated during render instead.
03

Write a reusable validation middleware for Express using zod.

Save to ▾
Show answer
Validate before the controller runs and return 400 on failure. import { z } from 'zod'; const validate = (schema) => (req, res, next) => { const result = schema.safeParse(req.body); if (!result.success) { return res.status(400).json({ errors: result.error.issues }); } req.body = result.data; next(); }; const userSchema = z.object({ name: z.string().min(2), email: z.string().email() }); router.post('/users', validate(userSchema), createUser);
04

What is NoSQL injection and how do you prevent it in a MongoDB app?

Save to ▾
Show answer
NoSQL injection happens when user input is placed directly into a query and contains operators. For example, a login body of { email: 'a@b.com', password: { $ne: null } } could match any password if passed straight to findOne. Prevent it by validating types with zod or Joi, casting inputs to strings, stripping keys starting with $ or containing dots, enabling Mongoose strictQuery, and never building $where queries from user input.
05

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

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

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; }
08

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

What strategies can you use to version a REST API?

Save to ▾
Show answer
The most common approach is URL versioning such as /api/v1/orders, which is simple and cache friendly. Alternatives are a custom header or a media type like Accept: application/vnd.app.v2+json, which keeps URLs clean but is harder to test in a browser. Whatever you choose, make additive changes without a new version, announce deprecations, and run old and new versions side by side during migration.
10

Write a useDebounce hook and use it for a search input.

Save to ▾
Show answer
The hook returns a value that updates only after a delay. function useDebounce(value, delay = 400) { const [debounced, setDebounced] = useState(value); useEffect(() => { const id = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(id); }, [value, delay]); return debounced; } function Search() { const [q, setQ] = useState(''); const term = useDebounce(q); const { data } = useFetch(`/api/products?search=${encodeURIComponent(term)}`); return <input value={q} onChange={(e) => setQ(e.target.value)} />; }
11

Props are being passed through five levels of components that do not use them. How would you refactor?

Save to ▾
Show answer
This is prop drilling. First try component composition: pass ready-made elements as children or props so intermediate components do not need the data. If many distant components need it, create a Context with a custom hook like useAuth. For complex or frequently updated shared state, use a store such as Zustand or Redux Toolkit with selectors so only interested components re-render.
12

When should you use useReducer instead of useState?

Save to ▾
Show answer
Use useReducer when state has multiple related fields, when the next state depends on complex logic, or when many event handlers update state in different ways. A reducer is a pure function (state, action) => newState that centralises transitions and is easy to unit test, for example a cart with add, remove, change quantity and clear actions. For simple independent values, useState is clearer.
13

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

How do you manage configuration and secrets differently for development and production in Node.js?

Save to ▾
Show answer
Keep config in environment variables, following the twelve-factor approach. Locally, load a .env file with dotenv or Node's built-in --env-file flag, and add .env to .gitignore while committing a .env.example. In production, inject variables through the hosting platform or a secrets manager. Validate all required variables at startup with a schema library like zod so the app fails fast with a clear message.
15

Walk me through a MERN project you built, end to end.

Save to ▾
Show answer
Situation: I built a job portal as my major project. Task: let recruiters post jobs and students apply with resumes. Action: I designed MongoDB collections for users, jobs and applications with indexes, built an Express REST API with JWT auth and role checks, a React frontend with React Router and TanStack Query, uploaded resumes to cloud storage, wrote API tests and deployed with Docker. Result: 60 classmates used it during placement season.
16

Why does the order of middleware matter in Express?

Save to ▾
Show answer
Express runs middleware and routes in the order they are registered. If express.json() is added after your routes, req.body will be undefined. Authentication middleware must come before protected routes, static file serving usually comes early, a 404 handler comes after all routes, and the error-handling middleware must be registered last. If a middleware neither sends a response nor calls next(), the request hangs.
17

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

Write a docker-compose file to run a MERN app locally.

Save to ▾
Show answer
Three services on one network: services: mongo: image: mongo:8 volumes: - mongo-data:/data/db api: build: ./server environment: MONGO_URI: mongodb://mongo:27017/shop JWT_SECRET: dev-secret ports: - "5000:5000" depends_on: - mongo web: build: ./client ports: - "8080:80" depends_on: - api volumes: mongo-data: The API reaches the database through the service name mongo.
19

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

What is MongoDB and how is it different from a relational database?

Save to ▾
Show answer
MongoDB is a document database that stores data as flexible JSON-like documents in collections instead of rows in fixed tables. Related data can be embedded in one document, so many reads need no joins. Schemas are flexible, horizontal scaling through sharding is built in, and it supports indexes, aggregation and multi-document ACID transactions. Relational databases fit strongly structured data with many relationships and complex joins.
21

How do you protect certain routes in React so that only logged-in users can access them?

Save to ▾
Show answer
Create a ProtectedRoute or layout component that reads auth state from context. If the user is not authenticated, return <Navigate to='/login' replace state={{ from: location }} />, otherwise render the Outlet or children. Show a loader while the session check is pending to avoid flicker. Remember that this is only for user experience; real protection must happen on the Express API by verifying tokens on every request.
22

What is CORS and how do you configure it in an Express API?

Save to ▾
Show answer
CORS is a browser security mechanism that blocks a page from reading responses from a different origin unless the server allows it through headers like Access-Control-Allow-Origin. For non-simple requests the browser sends a preflight OPTIONS request first. In Express, use the cors package with an explicit allowlist, for example cors({ origin: ['https://app.example.com'], credentials: true }), instead of allowing every origin with credentials.
23

Compress a large file with gzip using streams in Node.js.

Save to ▾
Show answer
pipeline connects streams and handles errors and cleanup. import { createReadStream, createWriteStream } from 'node:fs'; import { createGzip } from 'node:zlib'; import { pipeline } from 'node:stream/promises'; await pipeline( createReadStream('access.log'), createGzip(), createWriteStream('access.log.gz') ); Memory stays low regardless of file size, and backpressure is managed automatically.
24

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([...]).
25

What is the difference between process.nextTick, setImmediate and setTimeout(fn, 0)?

Save to ▾
Show answer
process.nextTick queues a callback to run right after the current operation, before Promise microtasks and before the event loop continues. setImmediate runs in the check phase after poll. setTimeout with 0 runs in the timers phase once at least about 1 ms has passed. Overusing nextTick recursively can starve I/O, so setImmediate is the safer way to defer heavy work.
26

Tell me about a time you found and fixed a security issue in an application.

Save to ▾
Show answer
Situation: while testing an order history feature, I noticed changing the order id in the URL showed another user's order. Task: fix this insecure direct object reference before release. Action: I added an ownership check in the service layer so queries always filter by the logged-in user's id, wrote tests for cross-user access, and reviewed similar endpoints. Result: we fixed four endpoints and added ownership checks to our review checklist.
27

What is the output order of console.log('A'), setTimeout(() => console.log('B'), 0), Promise.resolve().then(() => console.log('C')) and console.log('D')?

Save to ▾
Show answer
The output is A, D, C, B. A and D are synchronous and run immediately on the call stack. The Promise callback C goes into the microtask queue, and the setTimeout callback B goes into the macrotask queue. After the synchronous code finishes, the event loop drains all microtasks first, printing C, and only then runs the timer callback, printing B, even with a 0 ms delay.
28

Tell me about a time you disagreed with a teammate about an API design decision.

Save to ▾
Show answer
Situation: a frontend teammate wanted one endpoint returning the entire dashboard, while I preferred separate resource endpoints. Task: we needed a decision before sprint planning. Action: I listed trade-offs, measured payload size and call counts for both, and we agreed on resource endpoints plus one aggregated summary endpoint for the first screen. Result: the dashboard loaded faster and the API stayed reusable for the mobile app.
29

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
30

Explain the core ideas of Redux Toolkit.

Save to ▾
Show answer
Redux keeps global state in a single store that changes only through dispatched actions handled by reducers. Redux Toolkit is the official modern way to write Redux: configureStore sets up the store with DevTools, createSlice generates actions and reducers and lets you write mutating code safely using Immer, and createAsyncThunk handles async requests. RTK Query adds data fetching and caching. Components use useSelector and useDispatch.
31

How do you implement code splitting in a React app?

Save to ▾
Show answer
Use dynamic import with React.lazy, for example const Admin = lazy(() => import('./Admin')), and wrap it in a Suspense boundary with a fallback. Bundlers like Vite then create a separate chunk that loads only when needed. Split by route first, then by heavy components like charts, editors and maps. Preload likely next routes on hover, and check results with a bundle analyser such as rollup-plugin-visualizer.
32

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

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

How do you debug a JavaScript bug that a user reports but you cannot reproduce locally?

Save to ▾
Show answer
Situation: a user reported a checkout button doing nothing, but it worked for me. Task: find the cause quickly. Action: I asked for browser, device and steps, checked our error-tracking logs, and found a TypeError only on an older Safari version due to an unsupported method. I reproduced it on a cloud device, added a polyfill and a test. Result: the fix shipped the same day and I added browser targets to our build.
35

What is JSX and how is it converted into JavaScript?

Save to ▾
Show answer
JSX is a syntax extension that lets you write HTML-like markup inside JavaScript. A compiler such as Babel, esbuild or SWC, used by Vite, turns each element into a function call that creates a React element object. JSX supports JavaScript expressions inside curly braces, uses className instead of class and htmlFor instead of for, and requires one parent element or a fragment.
36

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

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

A MongoDB query on an orders collection takes 3 seconds. How do you diagnose and fix it?

Save to ▾
Show answer
Run the query with .explain('executionStats') and check whether the winning plan is COLLSCAN and how totalDocsExamined compares to nReturned. Create an index matching the filter and sort, following ESR, and confirm it becomes IXSCAN. Also project only needed fields, add limits, use lean in Mongoose, and check the profiler or Atlas Performance Advisor for other slow queries. Re-test with production-like data volume.
39

Your Node.js API runs on an 8-core server but CPU monitoring shows only one core busy. What do you do?

Save to ▾
Show answer
A Node process runs JavaScript on a single thread, so one instance uses about one core. Run multiple instances: PM2 in cluster mode with -i max, the cluster module, or several containers behind a load balancer such as Nginx. Because instances do not share memory, move sessions, caches and rate-limit counters to Redis, and make sure the app is stateless so any instance can serve any request.
40

How is Docker used in a MERN project?

Save to ▾
Show answer
Docker packages each service with its runtime and dependencies into an image so it runs the same on every machine. Typically the Express API gets an image built from a Node base, the React app is built and served by Nginx, and MongoDB runs from the official image locally while production often uses MongoDB Atlas. docker compose starts all services with one command, with networks and volumes for persistent data.

Frequently asked questions

What is asked in MERN Stack interviews for 2 years of experience?

At two years, questions move from what to why. Interviewers probe re-render issues, useEffect pitfalls, state management choices, MongoDB indexing and schema design, pagination, and secure token handling. You should explain trade-offs between approaches, describe a performance problem you solved, and show that you write tests. Some companies add a small take-home assignment covering both frontend and backend.

How many questions should I practise before a MERN Stack interview?

Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.

Which topics matter most for 2 years of experience?

Focus first on React performance, Schema design and indexes, Auth flows, Testing.