Skip to content
All articles

Node.js

Node.js Backend Interview Questions: What Strong Answers Cover

Representative Node.js backend interview questions covering the event loop, TypeScript boundaries, streams, concurrency, memory, and reliable production systems.

DeepRound team 5 min read

Node.js backend interviews often begin with promises and the event loop, then move toward production concerns: concurrency limits, runtime validation, memory behavior, data consistency, and reliable background work.

The questions below are not short facts to memorize. Use them to practice explaining the mechanism, identifying the failure mode, and choosing an approach for a concrete system.

1. What does an async function return?

An async function always returns a promise. Returning a plain value fulfills that promise with the value, while throwing an error rejects it.

async function answer() {
  return 42
}

const result = answer()

Here, result is a promise rather than the number 42. The caller can obtain the number with await answer() inside an async function or by handling the fulfilled value with .then().

A strong answer also explains error propagation. An exception thrown before or after an await becomes a rejected promise, so the caller must handle it with try/catch, a rejection handler, or an application-level error boundary.

2. How can one JavaScript thread handle many concurrent requests?

Node.js does not run every request from beginning to end before starting the next one. JavaScript callbacks execute on the event-loop thread, while many I/O operations are handled asynchronously by the operating system or Node.js worker pool. When an operation completes, its continuation becomes eligible to run on the event loop.

This works well when requests spend most of their time waiting for PostgreSQL, Redis, files, or network services. It does not make JavaScript execution parallel, and it does not make CPU-heavy work disappear. A long synchronous computation still prevents the event loop from running other callbacks.

Interviewers may follow up by asking about ordering. A complete answer distinguishes timers, I/O callbacks, promise microtasks, and process.nextTick() without reducing the event loop to a single FIFO queue.

3. When should CPU-heavy work leave the event-loop thread?

CPU-intensive JavaScript—such as parsing a very large document, transforming an image in JavaScript, or running a costly algorithm—can increase latency for every request handled by the same process.

The right response depends on the workload:

  • split occasional work into smaller bounded steps when practical;
  • use a pool of worker threads for CPU-intensive JavaScript that belongs in the service;
  • move long-running or operationally independent work to a background worker;
  • scale processes when the service needs isolation and use of multiple CPU cores.

Worker threads are not a general replacement for asynchronous I/O. They have startup, communication, memory, and failure-handling costs, so production systems normally reuse a bounded pool instead of creating a worker for every request.

4. Does a TypeScript assertion validate an HTTP request body?

No. A type assertion changes what the TypeScript compiler believes, but it does not inspect or transform a value at runtime.

type CreateUserInput = {
  email: string
  age: number
}

const input = req.body as CreateUserInput
await createUser(input)

The request may still contain a missing email, a string for age, or unexpected fields. A strong answer treats network input as unknown, validates it at the boundary, and only then passes a typed value into application code.

Validation should cover more than primitive types when the domain requires it. Email format, numeric ranges, allowed fields, request size, authorization, and safe error responses are separate concerns that a type assertion cannot solve.

5. What can go wrong with a large Promise.all batch?

This code starts work for every ID immediately:

const users = await Promise.all(ids.map((id) => loadUser(id)))

For ten IDs, that may be fine. For one hundred thousand, it can overwhelm a database pool, exceed an upstream rate limit, create a large number of pending objects, and increase memory use. If one input rejects, the promise returned by Promise.all() rejects, but operations that have already started are not automatically cancelled.

A strong answer proposes an explicit concurrency limit, sensible timeouts, cancellation where the underlying API supports it, and a decision about partial failure. The correct limit should come from downstream capacity and measurements rather than an arbitrary large number.

6. What is backpressure in a Node.js stream?

Backpressure prevents a fast producer from overwhelming a slower consumer. For a writable stream, write() returning false is a signal to stop producing temporarily and resume after the drain event.

Ignoring that signal allows data to accumulate in memory. The highWaterMark is a buffering threshold that helps control flow; it is not a hard memory limit.

In application code, pipeline() or async iteration often makes lifecycle and error handling clearer than manually wiring events. Whatever API you choose, a complete answer should cover errors, cancellation, cleanup, and what happens when the destination stops consuming data.

7. Why can memory keep growing after requests finish?

First separate JavaScript heap growth from total resident memory. A process can retain memory through reachable JavaScript objects, Buffer instances, native allocations, or fragmentation even when one heap metric looks stable.

Common causes include:

  • an unbounded cache or map;
  • event listeners that are never removed;
  • timers, closures, or pending promises retaining request data;
  • queues that accept work faster than consumers process it;
  • large buffers or native-library allocations.

A useful investigation compares memory over the same workload, checks heapUsed, external, arrayBuffers, and RSS, and uses heap snapshots or allocation profiles to find what remains reachable. A strong answer forms a hypothesis from evidence instead of calling every increase a memory leak.

8. Can a background job run exactly once?

If a worker performs a side effect and crashes before acknowledging the message, the broker may deliver it again. If it acknowledges first and crashes before the side effect, the work may be lost.

The usual design is therefore to make retries safe rather than claiming a universal exactly-once guarantee. Depending on the side effect, that may involve:

  • an idempotency key;
  • a uniqueness constraint or deduplication record;
  • a transactional outbox;
  • a provider API with idempotent operations;
  • reconciliation for effects that cannot share a transaction.

The important part is connecting the strategy to the failure boundary. Updating a database row, sending an email, and charging a card require different protections.

Turn questions into interview practice

Give each question an initial answer in two minutes. Then challenge it with a production constraint: a slow dependency, a burst of traffic, a partial failure, or a process restart. That second step reveals whether you understand the mechanism or only remember the headline.

You can also take the Node.js backend mock interview to practice these topics in a timed round and review feedback by competency.

We use optional cookies. Learn more in our Privacy Policy.