Skip to content
tezvyn:

Top 30 Intermediate Node.js & Express Interview Questions and Answers

30 intermediate multiple-choice Node.js & Express interview questions, past the definitions: how the pieces fit together, what breaks in practice, and the trade-off behind a choice. They come from 30 bites in the Node.js & Express library, the middle slice of the 134 Node.js & Express interview questions in the library. Answer them here or read straight down. Every question carries the correct option, why it is correct, and a link to the bite it came from.

Node.js, Express, Fastify, NestJS, Bun, Deno

30 questions. Pick an answer, or open “Show the answer” to read it.

Answers are graded in your browser. Nothing is saved, and no XP or streak is earned here. The app keeps score.

  1. Question 1 of 30

    In which event loop phase do the majority of completed I/O callbacks, such as a finished network read, actually execute?

    Show the answer

    Answer: d · The poll phase, which retrieves and runs I/O completions

    The poll phase retrieves new I/O events and executes most of their callbacks. The timers phase only handles elapsed setTimeout/setInterval, not I/O completion.

    Read the full bite: Order of the Node.js event loop phases

  2. Question 2 of 30

    When both are scheduled from inside a completed fs I/O callback, which runs first and why?

    Show the answer

    Answer: c · setImmediate, because the check phase follows poll in the same iteration

    From within an I/O callback the loop is in poll, so the next phase is check, making setImmediate fire before the timers phase comes around again. setTimeout(0) waits for the next iteration.

    Read the full bite: nextTick vs setImmediate vs setTimeout(fn, 0)

  3. Question 3 of 30

    Why does wrapping a heavy synchronous computation in an async function fail to keep a Node server responsive?

    Show the answer

    Answer: a · The computation is still synchronous and never yields the single JS thread

    async/await only helps when there is an awaited asynchronous boundary; a synchronous CPU loop still occupies the single thread, blocking the event loop. Worker Threads provide real parallelism.

    Read the full bite: Offloading CPU-bound work with Worker Threads

  4. Question 4 of 30

    What core problem does committing package-lock.json solve that package.json alone cannot?

    Show the answer

    Answer: c · It guarantees every install resolves to the exact same dependency tree, including transitive packages

    package.json uses version ranges, so installs can drift; the lockfile pins exact versions and tree shape for all transitive deps, making installs deterministic. It does not store tarballs or block additions.

    Read the full bite: Why package-lock.json must be committed

  5. Question 5 of 30

    Which statement about using ES Modules instead of CommonJS in Node is accurate?

    Show the answer

    Answer: d · ESM lacks __dirname by default and is enabled via type module or .mjs

    ESM omits __dirname and require, and is enabled by type module or the .mjs extension. import is not an alias for require, and only ESM supports top-level await.

    Read the full bite: Choosing between CommonJS and ES Modules

  6. Question 6 of 30

    Why should the service layer in a layered Express API avoid referencing the request and response objects?

    Show the answer

    Answer: d · It keeps business logic framework-agnostic and unit-testable without HTTP

    Keeping req and res out of services makes the business logic reusable and testable without spinning up HTTP. Express does not forbid it and services are not on a separate thread.

    Read the full bite: Layered structure for a scalable Express API

  7. Question 7 of 30

    Given a dependency written as ^1.4.2, which upgrade would npm refuse to install on its own?

    Show the answer

    Answer: b · 2.0.0, a major release

    The caret allows updates below the next major, so anything under 2.0.0 is permitted, but 2.0.0 itself is excluded. The tilde would be the operator that also blocks 1.7.0.

    Read the full bite: SemVer and the caret vs tilde range operators

  8. Question 8 of 30

    Why does the Promise callback print before the setTimeout(0) callback despite both being scheduled in the same tick?

    Show the answer

    Answer: d · The microtask queue is fully drained before the next macrotask runs

    After the synchronous stack clears, all microtasks (Promise reactions) drain before any macrotask (setTimeout) runs. The delay value is not the deciding factor here; queue priority is.

    Read the full bite: Output order of sync, microtask, and macrotask

  9. Question 9 of 30

    What happens to the remaining requests when one input to Promise.all rejects?

    Show the answer

    Answer: b · The combined Promise rejects immediately, but the other requests still run to completion

    Promise.all rejects as soon as the first input rejects, but JavaScript Promises are not cancellable, so the other in-flight requests continue running. They simply have no remaining handler.

    Read the full bite: Running independent requests with Promise.all and race

  10. Question 10 of 30

    In classic Express 4, how should an error from an awaited database call inside async middleware reach the error handler?

    Show the answer

    Answer: c · Catch it and pass it to next(err) so the error-handling middleware runs

    Express 4 does not auto-catch async throws, so you must catch and forward with next(err) to trigger the four-argument error handler. Throwing alone leaves the request hanging.

    Read the full bite: Handling async errors in Express middleware

  11. Question 11 of 30

    Why might a try/catch fail to catch an error from an async call inside it?

    Show the answer

    Answer: b · If the returned Promise is not awaited, control leaves the block before it rejects

    Without await, the async call returns a pending Promise and execution exits the try block immediately, so a later rejection is not caught there. Awaiting the call keeps it within the try/catch scope.

    Read the full bite: Comparing the three async error-handling styles

  12. Question 12 of 30

    Why does fs.createReadStream with readline scale to a 5GB log while fs.readFile does not?

    Show the answer

    Answer: d · Streaming keeps memory roughly constant by processing small chunks instead of buffering all 5GB

    Streaming processes the file in small chunks so memory stays flat; readFile allocates the whole file at once. It does not compress, double disk speed, and the issue is memory, not a hard 2GB open limit.

    Read the full bite: Counting lines in a 5GB log file efficiently

  13. Question 13 of 30

    Why must you wait for the request's end event before calling JSON.parse on a POST body?

    Show the answer

    Answer: d · The body arrives as multiple chunks and is only complete once end fires

    The request is a stream delivering chunks via data events; the full body exists only after end. The body is not encrypted, JSON.parse works anywhere, and req.body is not natively populated.

    Read the full bite: Reading a POST body from the request stream

  14. Question 14 of 30

    Given path.resolve('/foo', 'bar', '/baz', 'qux'), what is returned and why?

    Show the answer

    Answer: d · /baz/qux because an absolute segment discards everything to its left

    resolve processes right to left and a leading-slash segment resets the path, so /baz/qux results. join would keep /foo/bar/baz/qux; resolve does not simply concatenate or use only the last segment.

    Read the full bite: path.resolve vs path.join

  15. Question 15 of 30

    What is the core mechanism difference between fs.watch and fs.watchFile?

    Show the answer

    Answer: a · fs.watch uses OS-native change notifications; fs.watchFile polls with stat on an interval

    fs.watch is event-driven via OS APIs and efficient but inconsistent; fs.watchFile polls stat, making it portable but slower. The roles are not reversed and neither relies on encryption.

    Read the full bite: fs.watch vs fs.watchFile

  16. Question 16 of 30

    Where must app.use(express.json()) be registered for req.body to be available in a POST handler?

    Show the answer

    Answer: a · Before the route handlers, so the body is parsed when they run

    Middleware runs in registration order, so the parser must be mounted before the routes to populate req.body in time. Express does not reorder middleware, and registering it after the routes leaves req.body undefined.

    Read the full bite: Parsing JSON bodies with express.json

  17. Question 17 of 30

    When returning data from a JSON API, why is res.json preferred over res.end for sending an object?

    Show the answer

    Answer: d · res.json serializes the object and sets application/json, while res.end does neither

    res.json stringifies the object and sets the JSON content type; res.end sends raw data with no serialization or content-type help. res.end is not deprecated, and res.json does serialize rather than skip it.

    Read the full bite: res.send vs res.json vs res.end

  18. Question 18 of 30

    If app.get('/items/:id') is defined before app.get('/items/new'), what happens for a request to /items/new?

    Show the answer

    Answer: d · The /items/:id route matches first with id equal to 'new', shadowing the literal route

    Express matches in definition order with no specificity preference, so the earlier parameterized route captures 'new' as id. It does not auto-prefer the literal route, run both, or 404.

    Read the full bite: Express route order and matching

  19. Question 19 of 30

    You want authentication to apply only to routes under /admin without touching public routes. Which approach scopes it correctly?

    Show the answer

    Answer: d · router.use(requireAuth) on the admin Router, mounted at /admin

    Router-level middleware on a Router mounted at /admin runs only for that group. A global app.use would protect every route, and res has no use() method.

    Read the full bite: Application-level vs router-level middleware

  20. Question 20 of 30

    You wrote an error handler as (req, res, next) and errors passed via next(err) never reach it. What is the fix?

    Show the answer

    Answer: b · Declare four parameters: (err, req, res, next)

    Express identifies error handlers by their four-argument arity; three params make it a regular middleware that skips error propagation. Position alone does not fix the signature.

    Read the full bite: Express error-handling middleware signature

  21. Question 21 of 30

    Auth middleware sets the current user so the route handler can use it. What is the correct, concurrency-safe place to store that user?

    Show the answer

    Answer: a · On the req object, e.g. req.user

    req is unique per request and flows through the chain, so req.user is safe under concurrency. A shared module-level variable would leak one user's data into another simultaneous request.

    Read the full bite: Middleware execution order and sharing data via req

  22. Question 22 of 30

    You mount a products router with app.use('/products', router) and inside it write router.get('/products/:id', ...). Requests to /products/42 return 404. Why?

    Show the answer

    Answer: c · The path becomes /products/products/:id because the mount prefix is added again

    The mount path /products is prepended to each router path, so /products/:id inside the router resolves to /products/products/:id. Routes inside a router should be written relative to the mount point.

    Read the full bite: Modularize routes with express.Router

  23. Question 23 of 30

    A mobile client retries a request after a network timeout. Which request can it safely retry without risking duplicate resources?

    Show the answer

    Answer: b · PUT /users/42 with the full updated representation

    PUT to a known URL is idempotent, so a retry converges to the same state. POST is not idempotent, so retrying it can create duplicate users or orders.

    Read the full bite: Idempotency: PUT vs POST in REST

  24. Question 24 of 30

    GET /products/999 finds no such product, and separately a query fails because the database is unreachable. What status codes fit each case?

    Show the answer

    Answer: d · 404 for the missing product, 500 (or 503) for the database failure

    A missing resource is a client-facing 404; a server-side database outage is a 5xx (500, or 503 if transient). Using the same code for both would conflate normal misses with real incidents.

    Read the full bite: 404 vs 500: missing resource vs server failure

  25. Question 25 of 30

    BlogPost.findById(id).populate('author') returns author as a raw ObjectId instead of the full document. What is the most likely cause?

    Show the answer

    Answer: c · The author field in the schema is missing the ref to the Author model

    populate relies on the schema field declaring ref to know which model to fetch; without it, the id is not resolved. populate works on single refs and arrays alike, and it issues extra queries rather than a join.

    Read the full bite: Mongoose populate() for referenced documents

  26. Question 26 of 30

    Why are committed Sequelize migrations preferred over each developer manually altering their local database?

    Show the answer

    Answer: c · They give every environment the same ordered, reproducible, reversible schema changes

    Migrations are version-controlled scripts with up/down applied in order, so all environments converge on an identical schema with rollback support. They concern structure, not query speed, and are distinct from seeders.

    Read the full bite: Database migrations with the Sequelize CLI

  27. Question 27 of 30

    A pre('save') hook hashes the password every time without checking isModified('password'). What goes wrong when a user later updates only their email?

    Show the answer

    Answer: b · The already-hashed password is hashed again, so login stops working

    Without an isModified guard, any save re-hashes the stored hash, so the password no longer matches on login. Guarding with this.isModified('password') runs hashing only when the password actually changed.

    Read the full bite: Mongoose pre('save') hooks for password hashing

  28. Question 28 of 30

    Across five stateless Express instances behind a load balancer, what is the main operational cost of choosing session-based auth over JWTs?

    Show the answer

    Answer: a · You need a shared session store so any instance can resolve the session

    Session state must be reachable by whichever instance handles a request, so a shared store like Redis is required. JWTs avoid that by being self-contained and locally verifiable; sessions absolutely can scale, just with shared storage.

    Read the full bite: Session-based versus token-based authentication

  29. Question 29 of 30

    In a Passport local strategy verify callback, what is the correct way to signal a successful authentication?

    Show the answer

    Answer: d · return done(null, user)

    done(null, user) means no error and supplies the authenticated user, which Passport attaches to req.user. done(null, true) passes a boolean instead of the user object, so the session would lack a usable identity.

    Read the full bite: Securing Express with Passport local strategy

  30. Question 30 of 30

    In an Express 4 app, an async route handler throws an error but the centralized error middleware never runs. Why?

    Show the answer

    Answer: d · Express 4 does not auto-catch rejected promises, so the error must be forwarded via next(err)

    Express 4 only auto-catches synchronous throws; a rejected promise must be forwarded with next(err), typically via an asyncHandler wrapper. Express 5 awaits handlers and forwards rejections automatically.

    Read the full bite: Propagating async errors to Express error handlers

Could you explain these out loud?

That is what an interview actually tests. Tezvyn gives you questions like these with what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon