Skip to content

Senior Full Stack Engineer Interview Questions & Answers

40 questions for Senior Full Stack Engineer interviews — 20 medium, 20 hard.

About the Senior Full Stack Engineer role

A Senior Full Stack Engineer leads the technical direction of a product area. You design scalable architectures, choose libraries and patterns, plan migrations, set coding and security standards, and mentor the team. You work with product managers on estimates and trade-offs, handle critical incidents and make sure the system stays reliable as traffic grows. Deep knowledge of Node.js, MongoDB performance and React architecture is expected.

System design Scalability Code reviews Mentoring Incident handling
Solved for this roleStart 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

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

Write a reusable useFetch custom hook.

Save to ▾
Show answer
It returns data, error and loading state and aborts stale requests. function useFetch(url) { const [state, setState] = useState({ data: null, error: null, loading: true }); useEffect(() => { const controller = new AbortController(); setState((s) => ({ ...s, loading: true })); fetch(url, { signal: controller.signal }) .then((r) => { if (!r.ok) throw new Error(r.status); return r.json(); }) .then((data) => setState({ data, error: null, loading: false })) .catch((error) => { if (error.name !== 'AbortError') setState({ data: null, error, loading: false }); }); return () => controller.abort(); }, [url]); return state; }
03

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

What are streams in Node.js and what types exist?

Save to ▾
Show answer
Streams process data in chunks instead of loading it all into memory. The four types are Readable (file read, HTTP request), Writable (file write, HTTP response), Duplex (TCP socket) and Transform (gzip, encryption), which modifies data as it passes. Streams handle backpressure, pausing the source when the destination is slow. pipeline() from node:stream/promises connects them safely and handles errors and cleanup.
05

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

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

What are the important changes in Express 5 compared to Express 4?

Save to ▾
Show answer
Express 5 requires a modern Node version and forwards rejected Promises from async handlers to error middleware. Route path syntax moved to a newer path-to-regexp, so wildcards must be named, like /*splat, and regex-style characters in strings are no longer supported. Deprecated methods like res.send(status, body) were removed, req.query is read-only, and req.body is undefined unless a body parser runs.
08

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

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

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

Attackers are trying thousands of passwords on your login endpoint. How do you defend it?

Save to ▾
Show answer
Apply strict rate limits on login per IP and per account using a shared Redis store, add progressive delays or temporary account lockout after several failures, and show a CAPTCHA after repeated attempts. Return a generic error message so attackers cannot tell whether an email exists. Log and alert on spikes, check for breached passwords at signup, and offer two-factor authentication for sensitive accounts.
12

How do you implement graceful shutdown for a Node.js API during deployments?

Save to ▾
Show answer
Listen for SIGTERM and SIGINT. When received, stop accepting new connections with server.close(), let in-flight requests finish, close database connections with mongoose.connection.close() and other resources, then exit with code 0. Set a timeout, for example 10 seconds, after which you force exit. In Kubernetes or behind a load balancer, also fail the readiness check first so traffic stops reaching the instance.
13

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

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

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

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

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
18

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

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

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

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

What does a CI/CD pipeline for a MERN application usually include?

Save to ▾
Show answer
On every pull request, continuous integration installs dependencies with npm ci, runs lint, type checks, unit and integration tests, and builds the frontend. On merge to main, continuous delivery builds Docker images or artifacts, tags them with the commit, pushes to a registry, deploys to staging, runs smoke tests and then promotes to production, often with manual approval. GitHub Actions, GitLab CI and Jenkins are common tools.
23

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

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

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

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

Write a production Dockerfile for a Node.js Express API.

Save to ▾
Show answer
Keep the image small and run as a non-root user. FROM node:22-alpine WORKDIR /app ENV NODE_ENV=production COPY package*.json ./ RUN npm ci --omit=dev COPY src ./src USER node EXPOSE 5000 CMD ["node", "src/server.js"] Copying package files first lets Docker cache the dependency layer, so code changes rebuild quickly. Add a .dockerignore with node_modules and .env.
28

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

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

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

Tell me about a deployment that went wrong and what you learned.

Save to ▾
Show answer
Situation: a release broke login for all users because a new environment variable was missing in production. Task: restore service fast. Action: I rolled back to the previous version within ten minutes, added the variable, and redeployed after testing on staging. Afterwards I added startup config validation and a post-deploy smoke test for login in our pipeline. Result: no config-related outage has happened since.
32

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

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

How does React protect against XSS and where can XSS still happen?

Save to ▾
Show answer
React escapes values rendered in JSX, so user text containing script tags is shown as text, not executed. XSS can still happen through dangerouslySetInnerHTML with unsanitised HTML, javascript: URLs in href attributes, injecting user data into script tags during SSR, or unsafe third-party scripts. Sanitise any HTML with DOMPurify, validate URLs, set a Content Security Policy, and keep tokens out of JavaScript-readable storage.
35

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

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

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

How is Nginx used in front of a MERN application?

Save to ▾
Show answer
Nginx acts as a reverse proxy and web server. It serves the built React static files efficiently with caching and compression, proxies /api requests to Node instances, load balances across them, terminates HTTPS with certificates such as Let's Encrypt, and can apply rate limits and request size limits. For React Router, it uses try_files with a fallback to index.html so deep links work.
39

Describe a production issue you handled in a Node.js service.

Save to ▾
Show answer
Situation: our order API started timing out every evening at peak. Task: as the on-call developer I had to restore it. Action: I checked logs and metrics, found event-loop lag spikes, and traced them to a synchronous CSV export running inside a request. I moved the export to a background job and added event-loop lag alerts. Result: p95 latency dropped from 4 seconds to 300 ms and timeouts stopped.
40

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.

Frequently asked questions

What does a Senior Full Stack Engineer do?

A Senior Full Stack Engineer leads the technical direction of a product area. You design scalable architectures, choose libraries and patterns, plan migrations, set coding and security standards, and mentor the team. You work with product managers on estimates and trade-offs, handle critical incidents and make sure the system stays reliable as traffic grows. Deep knowledge of Node.js, MongoDB performance and React architecture is expected.

Which skills are tested in a Senior Full Stack Engineer interview?

Expect questions on System design, Scalability, Code reviews, Mentoring, Incident handling. Most rounds also ask you to explain a project and solve one practical problem live.

How do I prepare for a Senior Full Stack Engineer interview?

Revise Node.js Core, React Hooks, State and Performance, Authentication and Security, Testing, Deployment and DevOps first using the topic sheets, practise the questions on this page aloud, then do one full mock interview to practise timing.