The runtime & the event loop
Is Node.js single-threaded, and how does it handle concurrency?
Your JavaScript runs on a single thread, but Node offloads I/O (file, network, DNS) to libuv, which uses a thread pool and the OS. When an operation finishes, its callback is queued and the single thread picks it up. So Node is concurrent without your code being multithreaded.
What are the phases of the event loop?
libuv cycles through phases: timers (setTimeout/setInterval callbacks), pending callbacks, poll (retrieves new I/O events), check (setImmediate callbacks), and close callbacks. Between phases it drains the microtask queue (Promises, process.nextTick).
setImmediate vs setTimeout(fn, 0) vs process.nextTick?
process.nextTick runs before the event loop continues, ahead of Promises - overuse can starve I/O. setImmediate runs in the check phase, after the current poll phase. setTimeout(fn, 0) runs in the timers phase and is subject to a minimum delay, so its exact ordering versus setImmediate is not guaranteed.
Why must you avoid blocking the event loop?
Because all requests share one thread. A synchronous CPU-heavy task (a big loop, JSON.parse on a huge payload, synchronous crypto) freezes every other request until it finishes. Offload CPU work to worker threads, a child process, or a queue.
Asynchronous patterns
Callbacks vs Promises vs async/await?
Callbacks are the original pattern but nest into 'callback hell'. Promises represent a future value and chain with .then(). async/await is syntactic sugar over Promises that lets you write asynchronous code that reads synchronously, with try/catch for errors. They interoperate - await unwraps a Promise.
How do you run async operations in parallel?
Start them without awaiting, then await together: Promise.all waits for all (and rejects on the first failure), Promise.allSettled waits for all regardless of failures, and Promise.race resolves with the first to settle. Awaiting inside a loop runs them sequentially - usually a bug when they're independent.
What is the difference between the microtask and macrotask queues?
Macrotasks are timer and I/O callbacks handled by event-loop phases. Microtasks are resolved Promises and process.nextTick callbacks, and the queue is fully drained after each macrotask and between phases - so a resolved Promise callback runs before the next setTimeout.
How do you handle errors in async code?
In async/await, wrap awaits in try/catch. For Promises, always attach .catch(). Never let a Promise reject unhandled - listen for 'unhandledRejection' as a safety net, and in Express pass errors to next(err) so error-handling middleware can respond.
Streams, buffers & modules
What are streams and why use them?
Streams process data in chunks instead of loading it all into memory. Reading a 2 GB file with fs.readFile buffers the whole thing; piping a read stream to a write stream moves it chunk by chunk with a small, constant memory footprint. The four types are Readable, Writable, Duplex and Transform.
What is a Buffer?
A Buffer is a fixed-length chunk of raw binary data outside the V8 heap, used for I/O - file contents, network packets, encryption. You convert to and from strings with an encoding (utf8, base64, hex).
CommonJS require vs ES module import?
CommonJS (require/module.exports) loads modules synchronously and is the historical Node default. ES modules (import/export) are the standard, support static analysis and top-level await, and load asynchronously. A package opts in via "type": "module" or the .mjs extension.
What is the difference between dependencies and devDependencies?
dependencies are needed at runtime in production (Express, a database driver). devDependencies are only needed during development and testing (test runners, linters, build tools) and are skipped when you install with --production.
Express & the web layer
What is middleware in Express and why does order matter?
Middleware are functions with (req, res, next) that run in the order they're registered. Each can modify the request/response or end it, or call next() to pass control on. Order matters: a body parser or auth check must be registered before the routes that rely on it.
How do you write error-handling middleware?
Give it four arguments - (err, req, res, next) - and register it last. Express recognises the four-argument signature as an error handler and routes any error passed to next(err) or thrown in an async handler (wrapped) to it.
How would you scale a Node.js app across CPU cores?
Node uses one core per process, so run one process per core using the cluster module or a process manager like PM2, behind a load balancer. For CPU-bound work inside a request, offload to worker_threads so the main event loop stays responsive.
How to actually get ready
Reading these answers builds recognition; interviews test recall under pressure. Practise retrieving, not re-reading: take timed MCQ rounds on the event loop and async patterns first, because that's where interviewers probe deepest, then read the explanation for anything you miss and revisit it the next day.
Node rarely comes up alone. Full-stack rounds pair it with JavaScript fundamentals, a database (SQL or MongoDB), and often a React front end, so interleave those so you're ready when the interview jumps from 'explain the event loop' to 'now write the query behind this endpoint'.
Practice this now
