Node.js interview deep dive: what they are really testing
Almost every senior Node question is the same question wearing a different hat: do you understand that there is one thread doing your work, and do you know what happens when you block it.
Node interviews look varied and are not. Streams, timers, clustering, memory, async error handling: pull on any of them and you end up at the event loop, which is why interviewers use them as entry points.
The event loop, said properly
The definition everyone gives is that Node is non-blocking and asynchronous. True, and it does not demonstrate anything. The useful version explains the division of labour.
Your JavaScript runs on one thread. Input and output does not: it is handed to the operating system or to a thread pool, and when it completes, the callback is queued back onto that single thread. That is why thousands of idle connections cost almost nothing, and it is also why one expensive synchronous function stops every other request on that process, not just the one it belongs to.
What happens if a request handler does something CPU-heavy?
Junior answer
It will be slower and could block other requests, so you should avoid heavy computation in Node and move it to a background job or a different service.
Senior answer
It stalls the whole process for its duration, because there is one thread running JavaScript, so every other in-flight request waits behind it even though they are doing nothing. It also never shows up in local testing where you are the only user. I have watched a synchronous parse of a large payload add a few hundred milliseconds to every concurrent request. The fix is a worker thread if it is genuinely CPU work, or streaming it if the size is the problem.
Naming the concurrency effect, and the fact that it hides locally, is what makes this an operator's answer.
Where the ordering questions come from
Expect at least one question about what runs before what. The point is not trivia, it is whether you know microtasks and macrotasks are different queues.
A resolved promise callback runs before a timer that was already due, because the microtask queue is drained completely between each phase. The practically important consequence is that a recursive chain of promise callbacks can starve the loop entirely, and the process will look alive while serving nothing. That consequence is the answer worth having, more than the ordering itself.
Streams and backpressure
The most reliable senior discriminator in a Node interview, because it is the thing you only learn by having run out of memory in production.
Reading a large file into memory and sending it works fine until the file is large or ten users do it at once. Streaming processes it in chunks. But the question interviewers actually care about is what happens when the consumer is slower than the producer, and the answer is that without backpressure the chunks queue in memory until the process dies.
// No backpressure: 'data' keeps firing regardless of whether res can keep up
source.on("data", (chunk) => {
res.write(chunk);
});
// Backpressure honoured: pipeline pauses the source when the sink is full
await pipeline(source, transform, res);Say the words 'the writable returns false and you wait for drain', or simply use the pipeline helper and explain what it does for you, and you have answered a question most candidates talk around.
Async error handling
A favourite because there are several ways to lose an error silently, and each one is a real production incident someone has had.
- An async function called without await. The promise rejects with nobody listening. The request has already returned 200.
- Throwing inside a callback. Nothing up the stack catches it, because the stack it was called from is long gone.
- A try/catch around a promise you did not await. It catches nothing and looks like it does, which is worse than no handler.
- An error event on a stream with no listener. In Node this is not silent, it is fatal, which surprises people.
The strong closing point is what you do at the boundary: an unhandled rejection handler that logs and exits, with a supervisor restarting the process, because a process in an unknown state serving traffic is worse than a process that died cleanly.
Memory leaks
Usually asked as a debugging scenario rather than a definition: memory climbs over hours, what do you do.
The reasoning matters more than the tooling. Long-lived references are the cause: a module-level cache with no eviction, listeners added per request and never removed, closures held by a timer that outlives what it captured. Take two heap snapshots under load, compare, and look at what grew. Saying that you would compare snapshots rather than guess is most of the signal here.
Scaling a Node process
One process uses one core, so scaling means multiple processes, whether through the cluster module, a process manager or separate containers behind a load balancer.
The follow-up is what breaks when you go from one process to several, and it is always state. In-memory sessions, in-memory caches, in-memory rate limit counters and scheduled jobs all quietly assume there is exactly one of you. Naming that class of bug before being asked is a strong senior signal, and it is the same instinct system design rounds are testing.
Common questions
Do I need to know libuv internals?
No. You need the model: one JS thread, I/O offloaded, callbacks queued back. Internals only matter when they explain observable behaviour, like why file I/O uses a thread pool and network I/O generally does not.
Is the event loop question not a bit basic?
The question is basic. The follow-ups are not, and they are where the round is decided. Most candidates give a definition and cannot describe the consequences.
How much of this changes with worker threads?
It gives you a way to move CPU work off the main thread, so the model still holds, you just have more than one thread and a message boundary to reason about.