Node & Express
One thread, an event loop, and the middleware chain every request walks down.
One Thread, Many Requests
A thread-per-request server spends most of its life blocked, waiting on a database or a network call, with memory tied up per connection. Node instead starts the I/O, returns to the loop, and serves other requests while it waits — so a few megabytes of process handles thousands of concurrent connections that are mostly idle.
So Node is an excellent fit for I/O-heavy services — APIs, gateways, real-time connections — and a poor one for sustained computation, which belongs in a worker thread, a queue, or a different runtime.
The Event Loop
The loop runs in phases, draining a queue at each: timers, pending callbacks, poll for I/O, check (setImmediate), close handlers. Between every phase it empties the microtask queue — resolved promises and queueMicrotask — which is why an await continues before any timer fires.
console.log('1'); // synchronous
setTimeout(() => console.log('4'), 0); // timers phase, next tick of the loop
setImmediate(() => console.log('5')); // check phase
Promise.resolve().then(() => console.log('3')); // microtask, before any phase
console.log('2');
// 1 2 3 4 5 — microtasks drain before the loop moves on
Two practical consequences. A promise chain that never yields to I/O can starve the loop just as effectively as a blocking call. And ordering between setTimeout(fn, 0) and setImmediate is not guaranteed at the top level — if your logic depends on it, the logic is wrong.
Express & Middleware
Express is a thin routing layer over Node's HTTP server. Its one real idea is the middleware chain: an ordered list of functions, each receiving the request, the response and next, and each free to act, mutate, or end the chain.
const app = express();
app.use(express.json({ limit: '1mb' })); // parse body
app.use(requestId); // attach a correlation id
app.use(authenticate); // populate req.user, or 401
app.get('/orders/:id', async (req, res, next) => {
try {
const order = await orders.byId(req.params.id, req.user);
if (!order) return res.status(404).json({ error: 'not_found' });
res.json(order);
} catch (err) {
next(err); // hand to the error middleware
}
});
// four arguments marks this as the error handler — it must come last
app.use((err, req, res, _next) => {
req.log.error({ err }, 'request failed');
res.status(err.status ?? 500).json({ error: err.code ?? 'internal' });
});
| Rule | Why |
|---|---|
| Order is behaviour | Auth after the route it protects protects nothing |
| One error handler, last | Four-argument signature, one place to format failures |
| Always call next(err) | A swallowed rejection hangs the request until it times out |
| Bound body size | An unbounded parser is a denial-of-service invitation |
Running It In Production
One Node process uses one core. Production means running one process per core — via the cluster module, a process manager, or more commonly several containers behind a load balancer — and treating each as disposable.
| Concern | Practice |
|---|---|
| Configuration | Environment variables, validated at boot — fail fast on a missing one |
| Secrets | Injected at runtime, never in the image or the repo |
| Logging | Structured JSON to stdout, with a request id on every line |
| Shutdown | Handle SIGTERM: stop accepting, drain in-flight, then exit |
| Health | Separate liveness and readiness endpoints |
| Unhandled rejections | Log and exit — a process in an unknown state should be replaced |
Interview Questions
Why is Node good at concurrency with one thread?
It never blocks on I/O. Operations are handed to the OS and their callbacks resume later, so idle waiting costs almost nothing and thousands of mostly-idle connections fit in one process.
What happens when you do CPU work in a handler?
It blocks the event loop, so every other in-flight request stalls. That work belongs in a worker thread, a queue, or another service.
Why do promises resolve before timers?
Microtasks drain between event-loop phases, so a resolved promise continues before the loop reaches the timers phase.
What does the four-argument Express middleware mean?
It is the error handler. Registered last, it receives anything passed to next(err) and is the single place that formats failure responses.
What does graceful shutdown involve?
On SIGTERM, stop accepting new connections, let in-flight requests finish, close database pools, then exit. Without it, deploys drop requests mid-flight.
How do you use more than one core?
Run one process per core — cluster, a process manager, or multiple containers behind a load balancer. A single Node process will never use more than one.