Skip to content

Practical MERN Stack Interview Questions

46 questions with answers. Hands-on questions where you write code, a query, a formula or concrete steps.

How to approach these questions

Talk while you work: say your approach first, write the simplest correct version, then improve it and mention edge cases.

Solved — practicalStart 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

Write a debounce function in JavaScript.

Save to ▾
Show answer
Debounce delays a call until a quiet period has passed. Each new call clears the previous timer. function debounce(fn, delay = 300) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } const onSearch = debounce((q) => fetchResults(q), 400); It relies on a closure over timer and keeps this and arguments intact.
02

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

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

Group an array of order objects by their status using reduce.

Save to ▾
Show answer
reduce builds an object whose keys are statuses and values are arrays. const grouped = orders.reduce((acc, order) => { (acc[order.status] ||= []).push(order); return acc; }, {}); // { paid: [...], pending: [...] } Newer runtimes also offer Object.groupBy(orders, (o) => o.status), which returns the same shape with a null prototype.
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

Create a basic HTTP server in Node.js without Express that returns JSON.

Save to ▾
Show answer
The built-in node:http module is enough for simple servers. import http from 'node:http'; const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ status: 'ok' })); } res.writeHead(404); res.end(); }); server.listen(3000, () => console.log('Listening on 3000')); Express adds routing, middleware and body parsing on top of this.
08

Read a JSON file and write an updated version using fs/promises and async/await.

Save to ▾
Show answer
The promise-based fs API keeps code clean and non-blocking. import { readFile, writeFile } from 'node:fs/promises'; async function addUser(user) { try { const data = JSON.parse(await readFile('users.json', 'utf8')); data.push(user); await writeFile('users.json', JSON.stringify(data, null, 2)); } catch (err) { console.error('Failed to update file', err); throw err; } } Avoid readFileSync inside request handlers because it blocks the event loop.
09

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

Show how to create and use a custom EventEmitter.

Save to ▾
Show answer
Extend EventEmitter to emit domain events. import { EventEmitter } from 'node:events'; class OrderService extends EventEmitter { place(order) { // save order... this.emit('order:placed', order); } } const orders = new OrderService(); orders.on('order:placed', (o) => sendEmail(o.userEmail)); orders.once('order:placed', () => console.log('first order')); orders.place({ id: 1, userEmail: 'a@b.com' }); Listeners run synchronously in registration order.
11

How do you add a timeout to a fetch call in Node.js?

Save to ▾
Show answer
Node 18 and later ship a global fetch. Use AbortSignal.timeout to cancel slow requests. async function getJSON(url) { try { const res = await fetch(url, { signal: AbortSignal.timeout(5000) }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } catch (err) { if (err.name === 'TimeoutError') console.error('Request timed out'); throw err; } } Without a timeout, a hung upstream can hold connections open indefinitely.
12

How do you set up npm scripts for development and production in a Node.js API?

Save to ▾
Show answer
Define scripts in package.json and run them with npm run. "scripts": { "dev": "node --watch --env-file=.env src/server.js", "start": "node src/server.js", "lint": "eslint .", "test": "vitest run" } Modern Node has a built-in --watch flag, so nodemon is optional. npm start and npm test work without the run keyword.
13

Write an Express router with CRUD routes for a products resource.

Save to ▾
Show answer
Keep routes thin and delegate to a controller. import { Router } from 'express'; import * as ctrl from '../controllers/product.controller.js'; const router = Router(); router.get('/', ctrl.list); router.get('/:id', ctrl.getById); router.post('/', ctrl.create); router.patch('/:id', ctrl.update); router.delete('/:id', ctrl.remove); export default router; // app.js app.use(express.json()); app.use('/api/v1/products', router);
14

Write Express middleware that logs method, URL, status code and response time.

Save to ▾
Show answer
Hook into the response finish event to know the final status. function requestLogger(req, res, next) { const start = process.hrtime.bigint(); res.on('finish', () => { const ms = Number(process.hrtime.bigint() - start) / 1e6; console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${ms.toFixed(1)}ms`); }); next(); } app.use(requestLogger); In production, use a structured logger like pino instead of console.log.
15

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);
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 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
18

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

Write basic CRUD commands in mongosh for a users collection.

Save to ▾
Show answer
Core shell commands: db.users.insertOne({ name: 'Riya', email: 'riya@mail.com', age: 24 }) db.users.find({ age: { $gte: 21 } }, { name: 1, email: 1, _id: 0 }) db.users.updateOne({ email: 'riya@mail.com' }, { $set: { age: 25 } }) db.users.deleteMany({ isActive: false }) db.users.countDocuments({ city: 'Pune' }) The second argument of find is a projection that controls which fields are returned.
20

Write a Mongoose schema for a user with validation and timestamps.

Save to ▾
Show answer
A typical schema: import mongoose from 'mongoose'; const userSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true, maxlength: 60 }, email: { type: String, required: true, unique: true, lowercase: true }, password: { type: String, required: true, minlength: 8, select: false }, role: { type: String, enum: ['user', 'admin'], default: 'user' } }, { timestamps: true }); export const User = mongoose.model('User', userSchema); select: false hides the password from queries by default.
21

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

Create a compound index for a user's order history query and verify it is used.

Save to ▾
Show answer
Query: orders of one user, newest first. db.orders.createIndex({ userId: 1, createdAt: -1 }) db.orders.find({ userId: ObjectId('...') }) .sort({ createdAt: -1 }) .limit(20) .explain('executionStats') Check that the winning plan shows IXSCAN, not COLLSCAN, and that totalDocsExamined is close to nReturned. In Mongoose, declare it with orderSchema.index({ userId: 1, createdAt: -1 }).
23

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

Show the use of $push, $addToSet, $inc and $pull update operators.

Save to ▾
Show answer
Update operators change parts of a document atomically: db.posts.updateOne({ _id: id }, { $inc: { views: 1 } }) db.posts.updateOne({ _id: id }, { $addToSet: { tags: 'mongodb' } }) db.posts.updateOne({ _id: id }, { $push: { comments: { text: 'Nice', at: new Date() } } }) db.posts.updateOne({ _id: id }, { $pull: { tags: 'old' } }) $addToSet avoids duplicates, while $push always appends. Add $slice inside $push to cap an array's length.
25

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> ); }
26

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

Build a simple todo list in React with add and delete.

Save to ▾
Show answer
Update arrays immutably. function Todos() { const [todos, setTodos] = useState([]); const [text, setText] = useState(''); const add = (e) => { e.preventDefault(); if (!text.trim()) return; setTodos((t) => [...t, { id: crypto.randomUUID(), text }]); setText(''); }; const remove = (id) => setTodos((t) => t.filter((x) => x.id !== id)); return ( <form onSubmit={add}> <input value={text} onChange={(e) => setText(e.target.value)} /> <ul>{todos.map((t) => ( <li key={t.id}>{t.text} <button type='button' onClick={() => remove(t.id)}>x</button></li> ))}</ul> </form> ); }
28

Write a controlled login form that submits to an API.

Save to ▾
Show answer
One state object and a shared change handler. function LoginForm() { const [form, setForm] = useState({ email: '', password: '' }); const onChange = (e) => setForm({ ...form, [e.target.name]: e.target.value }); const onSubmit = async (e) => { e.preventDefault(); const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(form) }); if (!res.ok) alert('Invalid credentials'); }; return ( <form onSubmit={onSubmit}> <input name='email' value={form.email} onChange={onChange} /> <input name='password' type='password' value={form.password} onChange={onChange} /> <button>Login</button> </form> ); }
29

Set up React Router with a layout, a list page and a detail page using useParams.

Save to ▾
Show answer
Data router setup with nested routes: import { createBrowserRouter, RouterProvider, Outlet, useParams } from 'react-router'; const Layout = () => (<><Navbar /><Outlet /></>); const ProductPage = () => { const { id } = useParams(); return <ProductDetails id={id} />; }; const router = createBrowserRouter([ { path: '/', element: <Layout />, children: [ { index: true, element: <Home /> }, { path: 'products', element: <ProductList /> }, { path: 'products/:id', element: <ProductPage /> } ] } ]); export default function App() { return <RouterProvider router={router} />; }
30

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

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)} />; }
32

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

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 });
35

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

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);
37

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

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

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

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

Write a unit test for a function that calculates a cart total.

Save to ▾
Show answer
Vitest and Jest share the same syntax. // cart.js export const cartTotal = (items) => items.reduce((sum, i) => sum + i.price * i.qty, 0); // cart.test.js import { describe, it, expect } from 'vitest'; import { cartTotal } from './cart.js'; describe('cartTotal', () => { it('returns 0 for an empty cart', () => { expect(cartTotal([])).toBe(0); }); it('multiplies price by quantity', () => { expect(cartTotal([{ price: 100, qty: 2 }, { price: 50, qty: 1 }])).toBe(250); }); });
42

Write a React Testing Library test for a counter button.

Save to ▾
Show answer
Query by role and simulate real user events. import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Counter from './Counter'; test('increments the count when + is clicked', async () => { const user = userEvent.setup(); render(<Counter />); expect(screen.getByText('Count: 0')).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: '+' })); expect(screen.getByText('Count: 1')).toBeInTheDocument(); }); toBeInTheDocument comes from @testing-library/jest-dom.
43

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

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

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

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

Why are practical questions important?

Most technical rounds include at least one live task. Practising the exact typing and reasoning makes you calm and fast in the real round.

How should I answer them?

Talk while you work: say your approach first, write the simplest correct version, then improve it and mention edge cases.