Top 60 Express Interview Questions (2026)

The 60 Express questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is Express?

Key Takeaways

  • Express is a minimal, unopinionated web framework for Node.js that adds routing, middleware, and request/response helpers on top of the built-in http module.
  • Its whole design is the middleware pipeline: a request passes through an ordered chain of functions, each of which can read, change, respond, or hand off.
  • Interviews test whether you understand that pipeline, error handling, and routing, not whether you memorized method names.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery gets evaluated too.

Express is a minimal, unopinionated web framework for Node.js, first released in 2010 and now stewarded by the OpenJS Foundation. It doesn't hide Node's http module; it wraps it with routing, a middleware pipeline, and convenience helpers on the request and response objects, then gets out of your way. That thin design is why it stays popular: you assemble the pieces you need instead of accepting a full-stack framework's opinions. In interviews, Express questions center on one idea, the middleware chain, because routing, body parsing, authentication, and error handling all flow through it. The official Express documentation describes middleware as functions with access to the request, the response, and the next function in the cycle, and understanding that contract is what most Express questions are really checking. This page collects the 60 questions that come up most, each with a direct answer and runnable code. Increasingly the first backend round is a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
24Runnable code snippets you can practice from
45-60 minTypical length of an Express technical round

Watch: Node.js and Express.js - Full Course

Video: Node.js and Express.js - Full Course (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Express certificate.

Jump to quiz

All Questions on This Page

60 questions
Express Interview Questions for Freshers
  1. 1. What is Express and why is it used?
  2. 2. What is middleware in Express?
  3. 3. What are the req and res objects?
  4. 4. How does routing work in Express?
  5. 5. What is the difference between route parameters and query strings?
  6. 6. What are the ways to send a response in Express?
  7. 7. How do you parse a JSON request body in Express?
  8. 8. How do you serve static files in Express?
  9. 9. What is the difference between app.use() and app.get()?
  10. 10. What does the next() function do?
  11. 11. How do you set up a basic Express project?
  12. 12. Which HTTP methods does Express support, and how do they map to routes?
  13. 13. What is the difference between res.send() and res.json()?
  14. 14. How do you set an HTTP status code in Express?
  15. 15. How do you handle configuration like the port and environment in Express?
  16. 16. How do you handle 404 (not found) routes in Express?
  17. 17. What are template engines and how does Express use them?
  18. 18. What is express.Router() and why use it?
  19. 19. Is a route handler just a middleware?
  20. 20. What does Express add over the built-in Node http module?
  21. 21. How do you auto-restart an Express server during development?
Express Intermediate Interview Questions
  1. 22. How does error-handling middleware work in Express?
  2. 23. How do you handle errors in async route handlers?
  3. 24. Why does middleware order matter, and what breaks when it's wrong?
  4. 25. What are the different levels of middleware in Express?
  5. 26. What is CORS and how do you enable it in Express?
  6. 27. How do you add common security headers to an Express app?
  7. 28. How do you limit request body size and why?
  8. 29. How do you structure routes in a larger Express application?
  9. 30. Where does Express put incoming data, and what's the difference between req.params, req.query, and req.body?
  10. 31. How do you attach multiple handlers to a single route?
  11. 32. How do you add request logging in Express?
  12. 33. How do you build a simple REST API with Express?
  13. 34. What is res.locals and when do you use it?
  14. 35. How do you handle file uploads in Express?
  15. 36. How do you set response headers and perform redirects?
  16. 37. How do you work with cookies and sessions in Express?
  17. 38. How do you handle different configuration per environment?
  18. 39. How do you add basic rate limiting to an Express API?
  19. 40. How do you validate request input in Express?
  20. 41. How do you shut down an Express server gracefully?
Express Interview Questions for Experienced Developers
  1. 42. How does Express match a request to a route internally?
  2. 43. What changed between Express 4 and Express 5?
  3. 44. What actually happens when you call next()?
  4. 45. How do you scale an Express application across CPU cores?
  5. 46. How do you design error handling across a whole Express application?
  6. 47. How do you design a clean REST API in Express?
  7. 48. How do you implement authentication in Express?
  8. 49. How do you test an Express application?
  9. 50. How do you stream large responses in Express?
  10. 51. What happens if a route handler does heavy synchronous work?
  11. 52. How do you improve Express performance with compression and caching?
  12. 53. What does the 'trust proxy' setting do and when do you need it?
  13. 54. How do you harden an Express app for production?
  14. 55. When would you choose Express over NestJS or Fastify, and when not?
  15. 56. How do you integrate a database with Express cleanly?
  16. 57. Can Express handle WebSockets, and how?
  17. 58. How does the number of middleware affect performance, and how do you keep it lean?
  18. 59. An Express service is slow or throwing 500s in production. Walk through your approach.
  19. 60. How do you handle timeouts for slow requests and downstream calls?

Express Interview Questions for Freshers

Freshers21 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is Express and why is it used?

Express is a minimal, unopinionated web framework for Node.js. It sits on top of Node's built-in http module and adds routing, a middleware pipeline, and helper methods on the request and response objects. It handles the tedious parts of an HTTP server, matching routes and chaining middleware, without forcing an architecture on you.

Teams use it because it handles the tedious parts of an HTTP server, parsing URLs, matching routes, chaining middleware, without forcing an architecture. You keep control of structure while skipping the boilerplate of raw Node.

javascript
const express = require("express");
const app = express();

app.get("/", (req, res) => {
  res.send("Hello from Express");
});

app.listen(3000, () => console.log("listening on 3000"));

Key point: A one-line definition plus the phrase 'thin layer over Node's http module' beats a feature list. Interviewers open with this to hear how you frame the framework.

Watch a deeper explanation

Video: Express JS Crash Course (Traversy Media, YouTube)

Q2. What is middleware in Express?

Middleware is a function that runs during the request-response cycle with access to the request object, the response object, and the next function. It can read or change req and res, end the response, or call next() to pass control to the following middleware.

Everything in Express flows through this chain: logging, body parsing, authentication, routing, and error handling are all middleware. Understanding the chain is understanding Express.

javascript
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // hand off to the next middleware
});

Key point: Say the three-part signature (req, res, next) and that next() is what continues the chain. That one sentence answers half of every Express interview.

Watch a deeper explanation

Video: Learn Express Middleware In 14 Minutes (Web Dev Simplified, YouTube)

Q3. What are the req and res objects?

req is the request object: it holds incoming data like req.params (route parameters), req.query (query string), req.body (parsed body), and req.headers. res is the response object: you send data back with res.send(), res.json(), res.status(), and res.redirect(). Together they represent one HTTP exchange from arrival to reply.

Both wrap Node's raw IncomingMessage and ServerResponse and add convenience methods, so you rarely touch the raw Node objects directly.

javascript
app.get("/users/:id", (req, res) => {
  const id = req.params.id;      // route param
  const sort = req.query.sort;   // ?sort=name
  res.status(200).json({ id, sort });
});

Q4. How does routing work in Express?

A route pairs an HTTP method and a URL path with a handler function: app.get, app.post, app.put, app.delete, and so on. When an incoming request matches both the method and the path, Express runs that handler and stops looking. Routes are checked in the order you register them, so the first match wins.

Paths can be literal (/users), parameterized (/users/:id), or pattern-based. app.all matches every method, and app.route lets you chain handlers for one path.

javascript
app.get("/products", (req, res) => res.send("list products"));
app.post("/products", (req, res) => res.send("create product"));
app.get("/products/:id", (req, res) => res.send(`product ${req.params.id}`));

Key point: the key signal is 'method plus path plus handler'. Mention route parameters with the colon syntax and you have covered the follow-up.

Watch a deeper explanation

Video: How to build a REST API with Node js & Express (Programming with Mosh, YouTube)

Q5. What is the difference between route parameters and query strings?

Route parameters are named segments in the path itself, defined with a colon and read from req.params, so /users/:id captures the id. Query strings are key-value pairs after the ? in the URL, read from req.query, so /search?term=node gives you the term. One is part of the resource path, the other is optional extra data.

Use route params to identify a specific resource and query strings for optional filters, sorting, or pagination. That convention maps cleanly to REST.

javascript
// GET /users/42/posts?limit=10
app.get("/users/:userId/posts", (req, res) => {
  req.params.userId; // "42"
  req.query.limit;   // "10"
  res.end();
});
Route parameterQuery string
WhereIn the path (/users/:id)After ? (?sort=name)
Read fromreq.paramsreq.query
Typical useIdentify a resourceFilter, sort, paginate
Required?Usually yesUsually optional

Q6. What are the ways to send a response in Express?

res.send() sends a string, buffer, or object and sets sensible headers for you. res.json() serializes to JSON explicitly. res.status(code) sets the status. res.redirect() sends a redirect, res.sendFile() streams a file, and res.end() ends the response without a body. Each of these is a terminating method that finishes the request.

You call exactly one terminating method per request. Calling res.send() twice throws the 'headers already sent' error, one of the most common Express mistakes.

javascript
res.status(201).json({ created: true });
// res.send("again"); // would throw: headers already sent

Key point: The 'you can only respond once' rule is a favorite trap. Volunteering it shows you have hit the headers-already-sent error and understood why.

Q7. How do you parse a JSON request body in Express?

Register the built-in express.json() middleware, which reads the raw request body, parses it as JSON, and puts the result on req.body for your handlers to use. Without it, req.body is undefined for JSON requests because Express doesn't parse bodies by default. You opt in with this one line of middleware.

Since Express 4.16 this is built in, so you don't need the separate body-parser package anymore. Use express.urlencoded() for form-encoded bodies.

javascript
app.use(express.json());

app.post("/login", (req, res) => {
  const { email } = req.body; // populated by express.json()
  res.json({ email });
});

Key point: The classic gotcha: register express.json() before the routes that read req.body. Order in the middleware chain is the whole answer.

Q8. How do you serve static files in Express?

Use the built-in express.static middleware, pointing it at a directory on disk. Files in that folder are then served directly at the root path, or under a mount path you choose, without writing a route for each one. Express matches the URL to a file name and streams it back with the right Content-Type.

This is how you serve HTML, CSS, images, and client JavaScript without writing a route for each file.

javascript
// serve everything in ./public at the root
app.use(express.static("public"));

// or mount under a prefix
app.use("/assets", express.static("public"));

Q9. What is the difference between app.use() and app.get()?

app.use() mounts middleware that runs for any HTTP method whose path starts with the given prefix, which defaults to /. app.get() registers a handler that runs only for GET requests matching the exact path. So app.use() is for cross-cutting concerns like logging or auth, and the method functions handle the actual endpoints.

So app.use() is for cross-cutting middleware (logging, parsing, auth), and the method functions (get, post, put, delete) are for actual endpoints.

javascript
app.use("/api", authMiddleware);       // all methods under /api
app.get("/api/users", listUsers);       // only GET /api/users

Q10. What does the next() function do?

next() passes control to the next matching middleware or route in the stack. It's the mechanism that keeps the request-response cycle moving. Without it, a middleware that doesn't send a response leaves the request hanging, and the client eventually times out waiting for a reply that never comes.

Calling next('an error') or next(err) skips ahead to the error-handling middleware. Calling next('route') skips the rest of the current route's handlers and moves to the next matching route.

javascript
app.use((req, res, next) => {
  if (!req.headers.authorization) {
    return next(new Error("no token")); // jump to error handler
  }
  next(); // continue the chain
});

Key point: Knowing that next(err) routes to the error handler while plain next() continues normally is the distinction interviewers probe.

Q11. How do you set up a basic Express project?

Initialize a Node project with npm init, install Express with npm install express, then create an entry file that requires express, creates an app with express(), defines at least one route, and calls app.listen on a port. Running that file with node starts the server. From there you add middleware and routes as the app grows.

That's the whole minimum. From there you add middleware and routes as the app grows.

bash
npm init -y
npm install express
node index.js

Q12. Which HTTP methods does Express support, and how do they map to routes?

Express has a method function for each HTTP verb: app.get, app.post, app.put, app.patch, app.delete, app.head, app.options, and more. Each one registers a handler that runs only for requests using that verb at the matching path, so the same path can behave differently for a GET than for a POST.

In a REST API the mapping is conventional: GET reads, POST creates, PUT and PATCH update, DELETE removes. app.all handles a path for every method at once.

MethodREST intentExpress call
GETRead a resourceapp.get()
POSTCreate a resourceapp.post()
PUT / PATCHUpdate a resourceapp.put() / app.patch()
DELETERemove a resourceapp.delete()

Q13. What is the difference between res.send() and res.json()?

res.json() always serializes its argument to JSON and sets Content-Type to application/json, no matter what you pass. res.send() is more general: it sends strings, buffers, or objects, and when you hand it an object it serializes to JSON too. For an API, res.json() makes the intent explicit and handles edge cases like null cleanly.

In practice res.json() makes your intent explicit for API responses and also handles edge cases like sending null cleanly.

javascript
res.json({ ok: true });           // Content-Type: application/json
res.send("<h1>Hi</h1>");           // Content-Type: text/html

Q14. How do you set an HTTP status code in Express?

Call res.status(code) to set the status, then chain a terminating method that actually sends the response. For example, res.status(404).send('Not found') sets the code and sends the body in one line, and res.status(201).json(obj) does the same for a created resource. The status call on its own sends nothing.

res.sendStatus(code) sets the status and sends the standard status message as the body in a single call, handy for empty responses.

javascript
res.status(201).json({ id: 7 });   // created
res.sendStatus(204);               // No Content, empty body

Q15. How do you handle configuration like the port and environment in Express?

Read values from process.env, falling back to sensible defaults, like const port = process.env.PORT || 3000. Deployment platforms set PORT for you at runtime, and NODE_ENV tells you whether you're in development or production so you can toggle behavior. This keeps config out of the code and specific to each environment.

For local development, a package like dotenv loads a .env file into process.env. Keep secrets out of the repo and pass config in through the environment.

javascript
const port = process.env.PORT || 3000;
const isProd = process.env.NODE_ENV === "production";
app.listen(port);

Q16. How do you handle 404 (not found) routes in Express?

Add a catch-all middleware after all your routes. Because middleware runs in registration order, any request that didn't match an earlier route falls through to it, and there you respond with a 404. It has no special API; it's simply the last non-error middleware in the chain, so anything unmatched lands on it.

It's not a special API; it's just the last middleware in the chain. Placement is everything: put it after your routes, before the error handler.

javascript
// after all routes
app.use((req, res) => {
  res.status(404).json({ error: "Not found" });
});

Key point: The insight the question needs: 404 handling is 'the request reached the end of the chain unmatched', not a built-in feature. That reveals you understand ordering.

Q17. What are template engines and how does Express use them?

A template engine renders HTML from template files with embedded data placeholders. Express supports engines like EJS, Pug, and Handlebars: you set the view engine, put templates in a views folder, and call res.render() with the data to inject. Express runs the engine and sends the finished HTML back as the response.

For server-rendered pages this replaces string concatenation. Many modern Express apps skip it and serve JSON to a separate frontend instead.

javascript
app.set("view engine", "ejs");
app.get("/", (req, res) => {
  res.render("home", { name: "Asha" });
});

Q18. What is express.Router() and why use it?

express.Router() creates an isolated group of routes and middleware, a mini-application, that you mount onto a path with app.use. It keeps a growing app from becoming one giant file: related routes live in their own module, and the mount path prefixes all of them, so a users router mounted at /users owns every /users route.

You define related routes in their own module, export the router, and mount it with app.use('/users', usersRouter). Routes inside are relative to the mount path.

javascript
// users.js
const router = require("express").Router();
router.get("/", listUsers);       // GET /users
router.get("/:id", getUser);      // GET /users/:id
module.exports = router;

// app.js
app.use("/users", require("./users"));

Q19. Is a route handler just a middleware?

Yes. A route handler has the exact same (req, res, next) signature as middleware; the only difference is that a handler is bound to a specific method and path. A single route can even have several handlers that run in order, each calling next() to reach the one after it. Seeing that routes and middleware share one shape makes the framework click.

Seeing that routes and middleware are the same shape is a small insight that makes the whole framework click.

javascript
app.get("/dashboard", checkAuth, loadUser, (req, res) => {
  res.json({ user: req.user }); // runs after the two middlewares
});

Q20. What does Express add over the built-in Node http module?

The raw http module gives you a request and response and nothing else: you parse URLs, match methods, and set headers by hand. Express adds a router, the middleware chain, body parsing, static file serving, and dozens of response helpers.

You can build a server without Express, but you'd reinvent routing and middleware. Express is that common layer, standardized and battle-tested.

Q21. How do you auto-restart an Express server during development?

Use a tool like nodemon, which watches your source files and restarts the Node process automatically whenever you save, so you don't stop and start the server by hand after every edit. Recent Node versions also ship a built-in --watch flag that does the same thing without an extra dependency. Either one speeds up the edit-run loop.

It's a development convenience only; in production you run plain node behind a process manager or container.

bash
npx nodemon index.js
# or, recent Node:
node --watch index.js
Back to question list

Express Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: middleware judgment, error handling, async patterns, and the questions that separate users from understanders.

Q22. How does error-handling middleware work in Express?

Error-handling middleware has four arguments instead of three: (err, req, res, next). Express recognizes it purely by that four-argument signature, not by name, and calls it only when an error is passed via next(err) or thrown in a synchronous handler. You register it last so it can catch errors from every route and middleware above it.

You register it last, after all routes and other middleware, so it catches everything. One central error handler keeps error formatting consistent across the app.

javascript
// registered after all routes
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({ error: err.message });
});

Key point: The four-argument signature is the whole tell. Interviewers watch for whether you know Express detects error handlers by arity, not by name or registration method.

Watch a deeper explanation

Video: Express.js & Node.js Course for Beginners - Full Tutorial (freeCodeCamp.org, YouTube)

Q23. How do you handle errors in async route handlers?

In Express 5, a rejected promise from an async handler is forwarded to your error middleware automatically, so a plain async function with an await that throws just works. In Express 4 it doesn't: an unhandled rejection escapes Express entirely.

On Express 4 you wrap handlers in try/catch and call next(err), or use a small wrapper (or the express-async-errors package) that catches rejections for you.

javascript
// Express 4 wrapper pattern
const wrap = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get("/user/:id", wrap(async (req, res) => {
  const user = await db.find(req.params.id); // if this throws, wrap catches it
  res.json(user);
}));

Key point: Naming the Express 4 vs 5 difference here is a production signal. Many candidates don't know that unhandled async rejections silently escaped Express 4.

Q24. Why does middleware order matter, and what breaks when it's wrong?

Middleware runs in registration order. A body parser registered after a route means req.body is undefined in that route. An auth check registered after the protected route never runs. The error handler registered before the routes never sees their errors.

So the mental model is a pipeline: put parsing and logging first, then auth, then routes, then the 404 handler, then the error handler last.

A correct middleware ordering

1Body parsing and logging
express.json(), request logger
2Authentication
verify token, attach req.user
3Routes
your actual endpoints
4404 then error handler
catch-all, then the (err, ...) handler last

Almost every 'req.body is undefined' or 'auth never runs' bug traces back to registration order.

Key point: Give the concrete failure (req.body undefined because the parser ran too late). A specific broken case beats reciting 'order matters'.

Q25. What are the different levels of middleware in Express?

Application-level middleware is bound with app.use or app.METHOD and runs for the whole app. Router-level middleware is the same but bound to a router instance. Error-handling middleware has the four-argument signature. Built-in middleware ships with Express (express.json, express.static). Third-party middleware comes from npm (cors, helmet, morgan).

LevelBound toExample
Applicationappapp.use(express.json())
Routera Router instancerouter.use(auth)
Error-handlingapp or router, 4 argsapp.use((err, req, res, next) => {})
Built-inExpress itselfexpress.static('public')
Third-partynpm packagesapp.use(cors())

Q26. What is CORS and how do you enable it in Express?

CORS (cross-origin resource sharing) is a browser rule that blocks requests from a page on one origin to an API on another unless the API sends the right headers. When your frontend and API live on different domains, the browser enforces it.

The cors middleware sets those headers for you. Configure it to allow specific origins and methods rather than opening it to everyone in production.

javascript
const cors = require("cors");
app.use(cors({
  origin: "https://app.example.com",
  methods: ["GET", "POST"],
}));

Q27. How do you add common security headers to an Express app?

Use the helmet middleware, which sets a group of HTTP response headers that reduce common attack surface. It removes the X-Powered-By header that advertises Express, sets X-Content-Type-Options to stop MIME sniffing, configures a default Content-Security-Policy, and adds several others. One line gives you the defaults most apps should have.

It's one line and covers defaults most apps should have. You then tune the Content-Security-Policy for your own assets.

javascript
const helmet = require("helmet");
app.use(helmet());

Q28. How do you limit request body size and why?

Pass a limit option to the body parser, like express.json({ limit: '100kb' }), so Express rejects anything larger before it fully buffers. Without a cap, a client can send a huge payload that exhausts memory, which is a simple denial-of-service vector. The limit protects the process from both accidents and abuse.

Set the limit to the largest legitimate request your API accepts. Oversized requests then get rejected with a 413 before they're fully buffered.

javascript
app.use(express.json({ limit: "100kb" }));

Q29. How do you structure routes in a larger Express application?

Split routes into Router modules by resource, like users, orders, and products, each in its own file, then mount them under a base path with app.use. Add versioning by mounting under /api/v1. Keep handlers thin and push real logic into service functions, so the routing layer stays readable. This turns one giant file into a predictable folder structure.

This turns one thousand-line file into a predictable folder structure a new teammate can handle without a map.

javascript
app.use("/api/v1/users", require("./routes/users"));
app.use("/api/v1/orders", require("./routes/orders"));

Q30. Where does Express put incoming data, and what's the difference between req.params, req.query, and req.body?

req.params holds named route segments, so /users/:id gives req.params.id. req.query holds parsed query string values, so ?page=2 gives req.query.page. req.body holds the parsed request body, but only after a body-parsing middleware like express.json() has run. Each source maps to a different part of the incoming request.

A frequent bug: reading req.body without registering express.json() first, so it's undefined. Each source maps to a different part of the request.

javascript
// PATCH /users/42?notify=true  { "name": "Sam" }
app.patch("/users/:id", (req, res) => {
  req.params.id;    // "42"
  req.query.notify; // "true"
  req.body.name;    // "Sam" (needs express.json())
  res.end();
});

Q31. How do you attach multiple handlers to a single route?

Pass several functions to a single route and each runs in order, calling next() to hand off to the next one. This is how you compose per-route middleware, like a validation step, then an auth step, then the final handler that sends the response. It keeps cross-cutting checks out of the handler body and reusable across routes.

app.route() gives a cleaner form for one path across methods, and arrays of middleware let you reuse handler groups.

javascript
app.route("/articles")
  .get(listArticles)
  .post(validateBody, requireAuth, createArticle);

Q32. How do you add request logging in Express?

Write a small middleware that logs the method, URL, and response timing, or use the morgan package for ready-made access logs. Register it early in the chain so it sees every request, including ones that later fail or 404. Logging after the routes would miss anything that short-circuits before reaching it.

For production, structured JSON logs from a logger like pino feed better into log aggregation than plain text.

javascript
const morgan = require("morgan");
app.use(morgan("tiny")); // logs each request line

Q33. How do you build a simple REST API with Express?

Map each resource to routes by HTTP method: GET for reads, POST to create, PUT or PATCH to update, DELETE to remove. Parse bodies with express.json(), validate input, call your data layer, and respond with the right status code and JSON.

Return 201 on create, 204 on delete, 400 on bad input, and 404 when a resource isn't found. Consistent status codes are what makes an API predictable.

javascript
app.get("/todos", (req, res) => res.json(todos));
app.post("/todos", (req, res) => {
  const todo = { id: nextId(), ...req.body };
  todos.push(todo);
  res.status(201).json(todo);
});
app.delete("/todos/:id", (req, res) => {
  todos = todos.filter((t) => t.id !== Number(req.params.id));
  res.sendStatus(204);
});

Q34. What is res.locals and when do you use it?

res.locals is an object scoped to a single request and reset for the next one. Middleware can attach data to it, like the authenticated user or a generated request id, and later middleware, handlers, or rendered views can read it back. It's the clean way to pass computed values down the chain without touching global state.

It's the clean way to pass computed values down the chain for one request without polluting global state or the req object's own fields.

javascript
app.use((req, res, next) => {
  res.locals.requestId = crypto.randomUUID();
  next();
});
// later handler or view can read res.locals.requestId

Q35. How do you handle file uploads in Express?

express.json() only parses JSON, not multipart form data, so file uploads need a dedicated middleware like multer. It parses multipart/form-data requests, writes each uploaded file to disk or memory, and puts the file metadata on req.file or req.files for your handler. You then validate the file type and size before saving it.

You then validate file type and size before saving, because uploads are a common security and storage risk.

javascript
const multer = require("multer");
const upload = multer({ dest: "uploads/" });

app.post("/avatar", upload.single("photo"), (req, res) => {
  res.json({ file: req.file.filename });
});

Q36. How do you set response headers and perform redirects?

Use res.set(name, value) or res.header() to set a response header, and req.get(name) to read an incoming request header. res.redirect(url) sends a redirect that defaults to a 302 (temporary), and res.redirect(301, url) makes it a permanent one. You can set several headers at once by passing an object to res.set.

Set headers before sending the body. Once the response starts, you can't change headers, which is the root of the 'headers already sent' error.

javascript
res.set("Cache-Control", "no-store");
res.redirect(301, "/new-path");

Q37. How do you work with cookies and sessions in Express?

The cookie-parser middleware reads incoming cookies into req.cookies, and res.cookie() sets them on the response. For sessions, express-session stores just a session id in a cookie and keeps the session data server-side, in memory for development or in a shared store like Redis for production. The cookie identifies the session; the data lives on the server.

Set cookies with httpOnly and secure flags so client scripts can't read them and they only travel over HTTPS.

javascript
res.cookie("token", value, {
  httpOnly: true,
  secure: true,
  sameSite: "lax",
});

Q38. How do you handle different configuration per environment?

Branch on process.env.NODE_ENV: enable detailed error output and looser CORS in development, then tighten both in production so you don't leak stack traces or open your API to every origin. Keep the values themselves in environment variables, not hardcoded in the source, so the same build runs anywhere.

app.get('env') reads the same NODE_ENV. Load a .env file with dotenv locally, and let the deployment platform inject real values in production.

javascript
if (app.get("env") === "development") {
  app.use(errorDetailsMiddleware); // verbose errors locally
}

Q39. How do you add basic rate limiting to an Express API?

Use express-rate-limit as middleware. It counts requests per client, usually by IP address, inside a rolling time window and rejects anything over the limit with a 429 status. This blunts brute-force login attempts and scraping without any code in your handlers. Apply it globally or scope it to sensitive routes like login.

For anything distributed across multiple servers, back the limiter with a shared store like Redis so the count is global, not per process.

javascript
const rateLimit = require("express-rate-limit");
app.use(rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                 // per IP per window
}));

Q40. How do you validate request input in Express?

Validate in middleware before the handler runs, using a library like express-validator, Joi, or Zod. Check required fields, types, and formats, then respond with a 400 and a clear message when the input is bad, before any business logic touches it. Validating at the edge keeps handlers clean and shields the data layer from garbage.

Validating at the edge keeps handlers clean and protects the data layer from garbage. Never trust req.body, req.query, or req.params directly.

javascript
const { body, validationResult } = require("express-validator");

app.post("/signup",
  body("email").isEmail(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
    res.json({ ok: true });
  });

Q41. How do you shut down an Express server gracefully?

Capture the server object that app.listen returns, then listen for SIGTERM or SIGINT and call server.close(), which stops accepting new connections while letting in-flight requests finish. Once close completes, tear down database pools and other resources, then exit. This is what makes a deploy or scale-down drain cleanly instead of dropping active requests.

Without this, a deploy or scale-down can cut off active requests mid-response. Graceful shutdown is what makes rolling deploys safe.

javascript
const server = app.listen(3000);

process.on("SIGTERM", () => {
  server.close(() => {
    db.end();
    process.exit(0);
  });
});
Back to question list

Express Interview Questions for Experienced Developers

Experienced19 questions

advanced rounds probe internals, design judgment, and production scars. Expect every answer here to draw a follow-up.

Q42. How does Express match a request to a route internally?

Express keeps an ordered stack of layers, each pairing a path pattern with a handler. For each request it walks the stack top to bottom, compiling each route's path into a regular expression and testing method and path. The first matching layer runs, and next() advances to the next candidate.

So routing is linear and order-dependent, not a hash lookup. That's why a broad app.use() placed early can intercept requests meant for a later specific route.

Key point: Saying 'ordered stack, first match wins, paths compile to regex' shows you understand routing as sequential matching rather than magic dispatch.

Q43. What changed between Express 4 and Express 5?

The headline change is async error handling: Express 5 forwards a rejected promise from an async handler to the error middleware automatically, which Express 4 did not. Path matching also tightened, some deprecated method signatures were removed, and the router internals were modernized.

For interviews, the async-rejection behavior is the one that changes how you write handlers. In 5 you can drop most of the try/catch-plus-next boilerplate.

AreaExpress 4Express 5
Async handler rejectionEscapes Express, must wrapForwarded to error handler
Path matchingLooser patterns allowedStricter, safer patterns
Node baselineOlder versions supportedNewer Node required

Key point: The async-rejection difference is the answer that matters. Candidates who only know Express 4 keep writing wrappers Express 5 made unnecessary.

Q44. What actually happens when you call next()?

next() tells the router to invoke the next layer whose method and path match the request. Internally the router holds an index into its layer stack; next() advances that index and calls the corresponding handler. Passing an argument (next(err)) tells the router to skip ahead to the next error-handling layer instead.

Because it's a continuation, calling next() twice in one middleware runs the rest of the chain twice, a subtle bug that often shows up as 'headers already sent'.

Key point: The 'calling next() twice runs the chain twice' insight separates people who have debugged Express from people who have only read the docs.

Q45. How do you scale an Express application across CPU cores?

A single Node process uses one core. To use all cores, run one Express process per core behind a load balancer: Node's cluster module forks workers sharing a port, or you run multiple containers and let the orchestrator balance them. The second approach is more common in production because the platform handles restarts and health checks.

Either way, keep the app stateless, so any worker can handle any request. Push shared state (sessions, caches) into Redis or a database.

Scaling an Express service

1Make it stateless
sessions and cache in Redis, not process memory
2Run one process per core
cluster module or multiple containers
3Put a load balancer in front
distribute requests across workers
4Add horizontal replicas
scale out across machines as load grows

Statelessness comes first. Without it, adding processes just moves the bugs around.

Key point: Lead with 'make it stateless', then scaling. the key point is that you know a single Node process is one core before you reach for cluster.

Q46. How do you design error handling across a whole Express application?

Define an error class hierarchy (a base AppError with fields like statusCode and a public message, plus specific subclasses). Handlers throw or next() those errors. One central error middleware maps them to responses: known operational errors return their status and message, unknown errors return a generic 500 and get logged in full.

The line that matters is operational versus programmer errors: a missing record is a 404 you handle, a null dereference is a bug you log and fix. Leaking stack traces to clients is both a security and a professionalism failure.

javascript
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

app.use((err, req, res, next) => {
  if (err.isOperational) return res.status(err.statusCode).json({ error: err.message });
  console.error(err);
  res.status(500).json({ error: "Internal server error" });
});

Q47. How do you design a clean REST API in Express?

Model resources as nouns and use HTTP methods as the verbs: GET /orders, POST /orders, GET /orders/:id, PATCH /orders/:id, DELETE /orders/:id. Version the API under a prefix like /api/v1, return correct status codes, and keep every response consistent in shape so clients can rely on it. Predictable URLs and status codes are what make an API easy to consume.

Layer the code: routers hold routing only, controllers handle request and response, services hold business logic, and a data layer talks to the database. Thin controllers and reusable services keep the app testable as it grows.

javascript
// router -> controller -> service
router.get("/:id", ordersController.getById);

// controller
exports.getById = async (req, res, next) => {
  try {
    const order = await orderService.find(req.params.id);
    if (!order) return res.status(404).json({ error: "Not found" });
    res.json(order);
  } catch (e) { next(e); }
};

Key point: The layering answer (router, controller, service, data) is what the question needs for 'how would you structure this'. It shows you have maintained an Express app past the toy stage.

Watch a deeper explanation

Video: RESTful APIs in 100 Seconds // Build an API from Scratch with Node.js Express (Fireship, YouTube)

Q48. How do you implement authentication in Express?

Two common models. Sessions: on login you create a server-side session and set a session-id cookie; middleware loads the session on each request. JWT: on login you sign a token the client sends in an Authorization header; middleware verifies the signature and attaches the user, with no server-side session store.

JWT scales statelessly but is hard to revoke before expiry; sessions revoke instantly but need a shared store. Put the verification in middleware so every protected route reuses it.

javascript
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(401).json({ error: "Unauthorized" });
  }
}

app.get("/me", requireAuth, (req, res) => res.json(req.user));
ConcernSessionsJWT
StateServer-side storeStateless (in the token)
RevocationInstant (delete session)Hard before expiry
ScalingNeeds shared storeNo store needed
Best forServer-rendered appsAPIs, mobile clients

Q49. How do you test an Express application?

Test handlers and middleware in isolation as plain functions where you can, and test routes end to end with supertest, which drives the app in memory without binding a real port. Assert on status codes, response bodies, and side effects like a database write. Keep a few integration tests against a real test database for confidence.

Export the app separately from the code that calls app.listen, so tests import the app directly. Mock external boundaries (database, third-party APIs) and keep a few integration tests against a real test database for confidence.

javascript
const request = require("supertest");
const app = require("../app");

test("GET /health returns 200", async () => {
  const res = await request(app).get("/health");
  expect(res.status).toBe(200);
});

Key point: 'export app, call listen elsewhere' matters. Coupling app.listen into the same module is a common blocker.

Q50. How do you stream large responses in Express?

Because res is a Node writable stream, you can pipe a readable stream straight into it, like fs.createReadStream(path).pipe(res). That sends the data in chunks without ever loading the whole file into memory, and Node handles backpressure for you so a slow client doesn't overwhelm the server. It's how you serve large files or long exports safely.

For generated data, write chunks and call res.write() repeatedly, then res.end(). Streaming is how you serve large files or long database exports without blowing up memory.

javascript
app.get("/export", (req, res) => {
  res.setHeader("Content-Type", "text/csv");
  fs.createReadStream("big-report.csv").pipe(res);
});

Q51. What happens if a route handler does heavy synchronous work?

Node runs your handlers on a single event-loop thread. A synchronous CPU-heavy loop or a blocking call freezes that thread, so every other request in flight has to wait, no matter how many are queued behind it. Throughput collapses under one slow handler, and even health checks start timing out. This is the classic Node performance trap.

The fixes: move CPU work off the loop with worker threads or a separate service, use async I/O so waits don't block, and never call the sync versions of fs functions in a request path.

javascript
// bad: blocks the event loop for every request
app.get("/hash", (req, res) => {
  const digest = expensiveSyncHash(req.query.data); // freezes everyone
  res.send(digest);
});
// better: offload to a worker thread or async library

Key point: This is a Node question wearing an Express costume. the question needs 'single event-loop thread, blocking work stalls all requests', then the offload strategy.

Q52. How do you improve Express performance with compression and caching?

Enable gzip or brotli with the compression middleware to shrink response bodies over the wire. Set Cache-Control and ETag headers so clients and proxies can reuse responses. For expensive computed responses, cache the result in memory or Redis keyed by the request.

In production, terminate compression and TLS at a reverse proxy or CDN when you can, so Express spends its cycles on application work rather than encoding.

javascript
const compression = require("compression");
app.use(compression());

app.get("/report", (req, res) => {
  res.set("Cache-Control", "public, max-age=300");
  res.json(buildReport());
});

Q53. What does the 'trust proxy' setting do and when do you need it?

When Express runs behind a reverse proxy or load balancer, the client's real IP and protocol arrive in X-Forwarded headers, not on the socket. app.set('trust proxy', ...) tells Express to trust those headers so req.ip, req.protocol, and secure-cookie logic reflect the original client.

Get this wrong and rate limiting sees the proxy's IP for everyone, and secure cookies over HTTPS-terminated-at-the-proxy don't get set. It's a small setting with outsized production impact.

javascript
app.set("trust proxy", 1); // trust the first proxy hop
// now req.ip and req.protocol reflect the real client

Q54. How do you harden an Express app for production?

Layer defenses: helmet for security headers, cors locked to known origins, rate limiting on auth and write endpoints, body-size limits, input validation on every route, and secrets in environment variables. Never leak stack traces to clients, and keep dependencies patched.

Beyond middleware, run over HTTPS, set httpOnly and secure cookies, and log security-relevant events. No single setting is enough; production safety is the sum of these layers.

  • helmet for security headers and to hide X-Powered-By
  • cors restricted to explicit origins, not a wildcard
  • rate limiting on login and other sensitive routes
  • body-size limits and input validation on every endpoint
  • generic error responses to clients, full details only in logs

Q55. When would you choose Express over NestJS or Fastify, and when not?

Choose Express for small to mid services, prototypes, or when the team wants full control and the widest middleware ecosystem. Choose NestJS when a large team needs enforced structure, dependency injection, and TypeScript conventions out of the box. Choose Fastify when raw throughput and schema-based validation matter most.

The honest coverage names the cost of Express too: no enforced structure means discipline is on you, and its per-request overhead is higher than Fastify's. Picking on requirements rather than familiarity is the signal.

NeedReach forBecause
Full control, small teamExpressMinimal, largest ecosystem
Enforced structure, large teamNestJSDI, modules, TypeScript-first
Peak throughput, JSON validationFastifySchema-based fast serialization
Modern async middlewareKoaBuilt around async/await

Q56. How do you integrate a database with Express cleanly?

Keep the database out of your route handlers. Create a connection pool once at startup, expose data access through a repository or service layer, and have controllers call those functions instead of running queries inline. Reuse the pool across requests rather than opening a connection per request, which would exhaust the database. That boundary is what keeps the app testable.

Handle connection errors and pool exhaustion explicitly, and shut the pool down on graceful shutdown. Whether you use a query builder, an ORM, or raw queries, the boundary between web layer and data layer is what keeps the app testable.

javascript
// db.js: one pool for the process
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// service uses the pool
async function findUser(id) {
  const { rows } = await pool.query("SELECT * FROM users WHERE id = $1", [id]);
  return rows[0];
}

Q57. Can Express handle WebSockets, and how?

Express itself speaks HTTP, not the WebSocket protocol, but they share a server. You create the HTTP server, pass it to both Express and a WebSocket library like ws or Socket.IO, and the library handles the upgrade requests while Express handles normal routes.

So the technical answer is: Express handles the REST side, the WebSocket library handles the real-time side, and they coexist on one port through the same underlying http.Server.

javascript
const http = require("http");
const server = http.createServer(app);
const { WebSocketServer } = require("ws");
const wss = new WebSocketServer({ server });

wss.on("connection", (ws) => ws.send("connected"));
server.listen(3000);

Q58. How does the number of middleware affect performance, and how do you keep it lean?

Every registered middleware that matches a request runs on that request, so a long global chain adds work to every call. The fix is scoping: mount middleware only where it's needed (on a router or a route) instead of globally, and avoid redundant parsers or loggers.

Also skip work early: return fast for health checks, short-circuit auth on public routes, and don't parse bodies on GET requests. Placement and scope, not micro-optimizing each function, is where the wins are.

Key point: The insight is scope, not speed of individual functions. 'Mount middleware where it's needed instead of globally' is the answer that indicates production experience.

Q59. An Express service is slow or throwing 500s in production. Walk through your approach.

Observe first: check logs and metrics for which route and status, then look at request duration and error rate. Is the event loop blocked (rising latency across all routes), is one endpoint slow (a query or external call), or is it a spike in a specific error? Correlate with recent deploys and dependency changes.

Then confirm with a tool: an event-loop lag metric, an APM trace, or a heap snapshot for a memory climb. Fix at the right layer (a missing index, a blocking call, a missing timeout) and verify the metric returns to normal.

Key point: observe, hypothesize, verify, fix, confirm is the technical point. Reaching for one favorite tool before observing is the anti-pattern they watch for.

Q60. How do you handle timeouts for slow requests and downstream calls?

Set timeouts at every layer: a server-level request timeout so a stuck request doesn't hold a connection forever, and per-call timeouts on database queries and outbound HTTP so a slow dependency can't hang your handler indefinitely. On timeout, respond with a 503 or 504 rather than leaving the client waiting.

The failure to avoid is unbounded waits: without a downstream timeout, one slow third-party API can exhaust your connections and cascade into an outage.

javascript
// abort a downstream fetch after 3s
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 3000);
try {
  const r = await fetch(url, { signal: controller.signal });
} finally {
  clearTimeout(t);
}
Back to question list

Express vs Fastify, NestJS, and Koa

Express wins when you want a small, well-understood core and full control over structure, which fits most APIs and services. It trades built-in structure and raw throughput for simplicity and the largest middleware ecosystem in Node. Where it loses is opinionated architecture (NestJS gives you that), peak requests-per-second (Fastify's schema-based serialization is faster), and modern async ergonomics out of the box (Koa was built around async/await from the start). Naming these trade-offs out loud is itself an interview signal: it shows you pick a framework on merits, not habit.

FrameworkStyleBest atWatch out for
ExpressMinimal, unopinionatedAPIs, full control, huge ecosystemNo enforced structure, older callback style
FastifyMinimal, schema-firstHigh throughput, JSON validationSmaller ecosystem, plugin encapsulation to learn
NestJSOpinionated, structuredLarge teams, DI, TypeScript-firstHeavier, steeper learning curve
KoaMinimal, async-nativeModern middleware with async/awaitFewer built-ins, smaller community

How to Prepare for a Express Interview

Prepare in layers, and practice out loud. Most Express rounds move from concept questions to live coding a route or middleware, then to a design or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain the middleware chain without notes, then read one tier up for the stretch questions.
  • Type and run every snippet; building a two-route app with error handling teaches more than any explanation.
  • Practice thinking aloud on a small API with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical Express interview flow

1Recruiter or phone screen
background, motivation, a few concept checks
2Technical concepts
middleware, routing, error handling, async patterns
3Live coding
build a route or middleware and explain it under observation
4Design or debugging
API structure, trade-offs, reading unfamiliar code

Earlier rounds increasingly run as AI coding interviews. The middleware and routing concepts on this page are what gets probed at every stage.

Test Yourself: Express Quiz

Ready to test your Express knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Express topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass an Express interview?

They cover the question-answer portion well, but most Express rounds also include live coding: writing a route or middleware under observation while explaining your thinking. building a small API out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Which Express version do these answers assume?

Express 5, which is the current major line, with notes where 4 differed. The big change to know is that Express 5 handles rejected promises from async route handlers automatically, so you don't have to wrap every handler in try/catch and call next. If a version comes up, say you're answering for Express 5 and can speak to the 4 differences.

How long does it take to prepare for an Express interview?

If you already build with Node, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and build a small API daily; reading answers without typing them is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, the middleware chain, routing, error handling, and async control flow, and the phrasing takes care of itself.

Is there a way to test my Express knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It is the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Express questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 16 Apr 2026Last updated: 17 Jul 2026
Share: