Skip to the solution
MediumSystem Design6 min read

Explain the concept of 'event-driven architecture' in Node.js.

1.2k views

The problem

Question presented to candidate: "A new requirement arrives: every time an order is placed, a fraud-check service must also be notified β€” on top of the existing email and inventory services that already react to it. Does the order-placement code need to change to support this?"

What a strong answer should cover:

  • Event-driven architecture structures a system around components emitting events and other components subscribing to them, rather than the emitting component directly calling every interested party by name β€” Node's built-in EventEmitter (covered fully in its own dedicated question) is the standard, idiomatic implementation of this pattern within a single process.
  • πŸ“Œ Verified, not just described: a real publisher function with three independent subscribers, then a fourth subscriber added later, required zero changes to the publisher itself β€” confirmed directly, the fourth subscriber started receiving events immediately upon registration, with no modification to the code that emits them.
  • This is the concrete, direct answer to the prompt: no, the order-placement code does not need to change to add a fraud-check subscriber β€” it only needs to .on() the existing event, exactly as demonstrated.
  • The core benefit is decoupling: the publisher has no knowledge of who is listening, how many subscribers exist, or what they each do β€” verified directly by the fourth subscriber's addition requiring no publisher-side change at all. This is a different, looser coupling than a publisher directly calling emailService.send(), inventoryService.decrement(), and fraudCheck.screen() explicitly by name.
  • A precise answer names the trade-off honestly: this decoupling makes the overall flow harder to trace by reading the publisher's code alone β€” understanding everything that happens when an order is placed requires knowing every subscriber registered somewhere else in the codebase, which a direct, explicit call list would show in one place.
  • At a distributed-systems scale, the same underlying idea extends beyond a single process via a message broker (Kafka, RabbitMQ, or the BullMQ-style job queue covered in its own dedicated question) β€” the pattern is identical (a publisher emits, independent consumers subscribe), but the transport becomes a network-level broker instead of an in-process EventEmitter.

Clarifying questions expected:

  • "Is this a single-process, in-memory pattern (EventEmitter), or does it need to span multiple services/processes (a message broker)?" β€” the pattern is conceptually the same; the mechanism differs significantly.
  • "Is the concern adding a new subscriber, or understanding why an existing flow behaves the way it does?" β€” decides which direction of the demonstration matters most.

Code / implementation expected: Yes β€” the real, direct demonstration of adding a fourth subscriber with zero publisher changes is the concrete, convincing proof of the core decoupling benefit, not a description of it.

  • event-driven
  • architecture
  • event loop
  • scalability

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 8 sections

Read the code

JavaScriptA real EventEmitter publisher: a fourth subscriber added later requires zero changes to the publisher itself
const EventEmitter = require("events");
const bus = new EventEmitter();

bus.on("order.placed", (order) => console.log("[email service]", order.id));
bus.on("order.placed", (order) => console.log("[inventory service]", order.id));
bus.on("order.placed", (order) => console.log("[analytics service]", order.id));

function placeOrder(order) {
  console.log("[order service] order placed:", order.id);
  bus.emit("order.placed", order); // no knowledge of who is listening
}

placeOrder({ id: 42 }); // 3 subscribers fire

// LATER, with ZERO changes to placeOrder:
bus.on("order.placed", (order) => console.log("[fraud-check service]", order.id));
placeOrder({ id: 43 }); // all 4 subscribers fire, including the new one

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 40 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextMedium

What is CSRF and how do you protect a Node.js API against it?

Continue