Question presented to candidate: "An operation can either take an error-first callback, or be modeled as an EventEmitter firing 'data'/'error' events. If the caller forgets to handle a failure in each style, does the same thing happen both times?"
What a strong answer should cover:
- Error-first callback convention —
fn(...args, (err, result) => {})— and EventEmitter-based patterns —.emit("data", result)/.emit("error", err)— are Node's two standard idioms for delivering an async result: one single callback invocation for a one-shot operation, versus multiple, ongoing events for something that can fire more than once (a stream's repeated'data'events, a socket's connection lifecycle). - 📌 The critical, verified behavioral difference between the two, when a failure is not handled: a callback invoked with an
Errorthat the caller's own callback body simply ignores produces no crash at all — verified directly, the error is silently swallowed, and the script continues normally. Emitting'error'on anEventEmitterwith no listener attached, by contrast, throws synchronously — verified directly as a real, caught exception — and, left uncaught further up, crashes the process. - This is not a minor implementation detail — it is a deliberate design choice: error-first callbacks put the burden of checking
errentirely on the caller, with no enforcement;EventEmitter's'error'special case (covered fully, with its own live verification, in the dedicated EventEmitter-basics question) makes ignoring a failure impossible to do silently — it becomes a loud crash instead. - The choice between the two patterns in your own API design should track the shape of the result: a single, one-time outcome (reading a file once) fits an error-first callback (or, in modern code, a Promise) naturally; a stream of ongoing events (a socket receiving many messages over its lifetime, a long-running watcher) fits
EventEmitternaturally. - A precise answer connects this to Node's broader evolution: Promises/
async-await(covered in their own dedicated questions) have largely superseded error-first callbacks for single-result async operations in modern code, whileEventEmitterremains the standard, unreplaced idiom for ongoing, multi-event sources precisely because Promises only ever resolve/reject once. - The takeaway worth stating explicitly: silently ignorable errors are a real, structural risk of the error-first callback pattern specifically — a caller who writes
fn(() => {}), discarding theerrparameter entirely, introduces no visible symptom until the swallowed failure causes a much harder-to-trace problem downstream.
Clarifying questions expected:
- "Is the operation genuinely one-shot, or does it produce results/events repeatedly over time?" — the deciding factor for which pattern actually fits.
- "Is the concern designing a new API, or understanding why an existing one behaves the way it does on a missed error?"
Code / implementation expected: Yes — the actual, verified contrast between a silently-ignored callback error (no crash) and an unhandled 'error' emit (a real synchronous throw) is the concrete, convincing deliverable.