Event-driven architecture is one of the most misapplied labels in distributed systems — plenty of teams publish messages to a queue and call it event-driven while keeping every service as tightly coupled as a synchronous call chain, just with extra latency and a broker in the middle. Architect-level interviewers use this topic to separate people who've actually designed an eventing backbone from people who've configured a message queue: expect precise questions on what makes something an event versus a command, how much state an event should carry, and hard trade-offs between competing Azure and open-source messaging technologies. You should also expect scenario questions about partition design, schema evolution and what happens when a consumer falls behind or a message arrives twice. This page covers events versus commands, event notification versus event-carried state transfer, Kafka partitioning and consumer groups, schema registries, choosing between Azure Event Grid, Event Hubs and Service Bus, and event storming as a design technique.

Q1 What's the difference between an event and a command? Why does mixing them up produce an "event-driven" system that isn't actually decoupled?#

Short answer: A command is an instruction — do this — addressed to a specific, known recipient who is expected to act on it and can reject it; an event is a fact — this happened — broadcast with no addressee and no expectation that anyone in particular reacts to it, and the publisher doesn't know or care who's listening. Mixing them up usually means publishing what's syntactically an event but is semantically a command, which leaves the producer implicitly coupled to a specific consumer's behavior even though the wiring looks decoupled.

The test that actually distinguishes them is imperative versus past tense, and what happens if nobody's listening. ShipOrder is a command — it names an intended action, targets a specific handler, and if no one processes it, something is broken and needs to be retried or escalated. OrderShipped is an event — it states a fact about something that already happened, any number of services (zero, one or many) may react to it, and if nobody's currently listening, that's not automatically a problem, because the fact remains true regardless. A command implies the sender is ordering a specific downstream reaction; an event implies the sender doesn't know or care who's downstream.

The coupling problem shows up when a service publishes an event but shapes or times it around exactly what one specific downstream consumer needs to do next — effectively encoding "and now you, inventory service, should reserve stock" into what's formatted as a broadcast fact. That's a command wearing an event's clothing: the producer is still implicitly coupled to the consumer's behavior and has to change if that consumer's logic changes, it's just harder to see because there's a broker in between instead of a direct call. Genuine decoupling means the order service publishes OrderPlaced because that's true and useful information regardless of who cares, and the inventory service independently decides, based on its own business rules, that an OrderPlaced event means it should attempt a reservation — a decision the order service never needs to know about.

What interviewers look for: the imperative-versus-past-tense framing plus, more importantly, recognizing disguised commands — this is the single most common event-driven-architecture mistake interviewers probe for.

Common mistakes: naming events after what you want the consumer to do, such as ReserveInventory, instead of what actually happened, such as OrderPlaced — the clearest tell of a hidden command.

Q2 Explain event notification versus event-carried state transfer. What are the trade-offs, and when would you choose a thin event over a fat one?#

Short answer: Event notification publishes a thin event — essentially "this thing happened, here's its ID" — and expects interested consumers to call back to the source service for any details they need. Event-carried state transfer (ECST) publishes a fat event that carries the actual data a consumer would need, so consumers can act without a synchronous call back to the source. The trade-off is coupling and staleness versus payload size and duplication: thin events stay small and always accurate but reintroduce a dependency on the source service; fat events remove that dependency but risk consumers acting on stale, duplicated data that drifts from the source over time.

Event notification is the more conservative default: an OrderPlaced event carrying only an order ID tells consumers an order exists, and if the inventory service needs the line items to reserve stock, it calls the order service's API to fetch them. This keeps the event tiny and guarantees the data fetched is current at the moment of the call, but it reintroduces exactly the kind of synchronous coupling and availability dependency event-driven architecture is often adopted to avoid — if the order service is down, the inventory service can't act on an event it already received, because it can't fetch the details it needs.

Event-carried state transfer avoids that call entirely by including the customer, line items and shipping address directly in the event, giving the inventory service everything it needs to act immediately with no dependency on the order service being reachable at that moment. The cost is real: every consumer that cares about order data now holds its own copy, which can drift out of sync if the order is later modified and that change isn't itself republished as a new event; the payload grows as more consumers need more fields, coupling the event's shape to an increasing number of unrelated consumers' needs; and duplicated data across services makes "what's the current shipping address, really" a genuinely harder question to answer than it was with a single source of truth.

The practical choice usually comes down to how tolerant the consumer can be of staleness and how much it needs the data versus just needing to react. High-frequency, low-latency consumers that need to act autonomously and can tolerate eventual correction typically favor event-carried state transfer; consumers that need an authoritative, always-current view, or that only occasionally need full details, favor thin notification events plus an on-demand fetch.

What interviewers look for: the specific trade-off — coupling and availability dependency versus staleness and data duplication — not just "one is bigger than the other," and a sense of which consumer characteristics push toward each choice.

Follow-up questions:

  • How would you handle a downstream consumer that needs data from an event-carried-state-transfer event after the source aggregate has since changed?
  • What's a reasonable strategy for keeping a fat event's schema from growing without bound as more consumers attach requirements to it?

Q3 How do Kafka partitions and consumer groups work together to provide both ordering and horizontal scalability? What happens during a rebalance?#

Short answer: A Kafka topic is split into partitions, each an ordered, append-only log; Kafka guarantees ordering only within a partition, never across the whole topic, which is what lets multiple partitions be consumed in parallel. A consumer group is a set of consumer instances sharing a group ID, and Kafka assigns each partition to exactly one consumer within that group at a time — so the group as a whole scales horizontally up to the partition count, while each individual partition's messages are still processed in order by whichever single consumer owns it.

This split is the entire mechanism behind Kafka's throughput story: a topic with twelve partitions can be consumed by up to twelve consumer instances in the same group, each independently reading and committing offsets for its own subset of partitions, with no coordination needed between them for the data itself. Add a thirteenth consumer to that group and it sits idle, because there's no partition left to assign it; remove consumers and Kafka redistributes their partitions among the survivors. Ordering is preserved per key, not globally, because messages with the same partition key always land on the same partition — which is why choosing a good partition key, covered next, is one of the most consequential decisions in a Kafka-based design.

C#
var config = new ConsumerConfig
{
    BootstrapServers = "broker1:9092,broker2:9092",
    GroupId = "inventory-service",
    AutoOffsetReset = AutoOffsetReset.Earliest,
    EnableAutoCommit = false,
};

using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe("order-events");

while (!cancellationToken.IsCancellationRequested)
{
    var result = consumer.Consume(cancellationToken);
    await ProcessOrderEventAsync(result.Message.Value);
    consumer.Commit(result); // commit only after successful processing
}

A rebalance happens whenever group membership changes — a consumer joins, leaves, crashes, or is considered dead because it stopped sending heartbeats — and it's a stop-the-world event for the affected partitions: consumption pauses while the group coordinator reassigns partitions among current members, and any consumer that loses a partition it was mid-processing needs to have committed its offset recently enough that reprocessing a small, bounded amount of work, not the whole partition, is acceptable. This is exactly why committing offsets only after successful processing, as in the example above, matters: auto-commit on a timer can advance the offset past messages that were read but not actually finished processing, so a rebalance or crash silently loses work instead of merely reprocessing it.

What interviewers look for: the per-partition, not per-topic, ordering guarantee stated precisely, and a clear mental model of what a rebalance actually pauses and reassigns, not just "consumers share the load somehow."

Common mistakes: assuming Kafka provides total ordering across a topic, or leaving auto-commit enabled on a consumer that does meaningful work per message, which creates a silent-data-loss window on rebalance or crash.

Q4 How do you choose a partition key in Kafka or Event Hubs, and what goes wrong when you choose badly?#

Short answer: The partition key should be whatever entity you need strict ordering for and nothing broader — typically an aggregate or entity ID like an order ID — because every message sharing that key is guaranteed to land on the same partition and be processed in order by a single consumer. Choosing a key that's too coarse, such as a fixed tenant ID for a large tenant, creates a hot partition that one consumer can't keep up with, while choosing no consistent key at all loses ordering guarantees entirely.

The hot-partition failure mode is the one interviewers push on hardest, because it's the one that actually happens in production: if you partition by tenant and one tenant generates ten times the traffic of every other tenant combined, that tenant's partition becomes a bottleneck no amount of adding consumers can fix, because Kafka will never assign two consumers in the same group to one partition — you're capped at exactly one consumer's throughput for that tenant's entire event stream, regardless of how many idle consumers sit on other partitions. The fix is picking a key granular enough to spread load evenly, often the entity being acted on rather than a coarser grouping, while still being coarse enough to give you the ordering guarantee you actually need; if you only need per-order ordering, keying by order ID gives you that without concentrating an entire tenant's traffic on one partition.

C#
var message = new Message<string, string>
{
    Key = order.Id, // same order's events always land on the same partition
    Value = JsonSerializer.Serialize(new OrderPlacedEvent(order)),
};
await producer.ProduceAsync("order-events", message);

Choosing no key at all, letting the producer round-robin across partitions, maximizes throughput distribution but gives up ordering entirely — fine for genuinely independent, order-insensitive events, and wrong the moment a consumer's correctness depends on seeing one event before another for the same order. The general principle is that partition key choice is a correctness decision disguised as a performance one: get it wrong and you either silently break ordering guarantees your consumers assume, or you create a single-consumer bottleneck that no amount of horizontal scaling elsewhere in the system can fix.

What interviewers look for: the hot-partition mechanism explained precisely — why adding consumers doesn't help a saturated single partition — and the framing of partition key choice as a correctness, not just throughput, decision.

Q5 What problem does a schema registry solve, and what's the difference between backward, forward and full compatibility?#

Short answer: A schema registry is a central service that stores and versions the schemas, typically Avro, Protobuf or JSON Schema, for events flowing through a messaging platform, validating a producer's message against a registered schema before it's published and letting consumers look up the exact schema a given message was written with. It solves independently deployed producers and consumers silently breaking each other when one side changes a message's shape without coordinating with everyone downstream.

Without a schema registry, schema evolution is a purely social problem — someone changes a field's type or removes a field, deploys, and finds out from an on-call page that several consuming services threw deserialization exceptions in production. The registry turns that into an enforced contract: producers register a new schema version before they can publish with it, and the registry rejects a proposed change that violates the compatibility rule configured for that topic, catching the break at deploy time or even at build time instead of at message-consumption time in production.

Compatibility modeWhat's allowedWho's protected
BackwardNew schema can read data written with the previous schema.Consumers upgrading after producers.
ForwardOld schema can read data written with the new schema.Consumers upgrading before producers.
FullBoth backward and forward hold simultaneously.Producers and consumers upgrading in any order.

Backward compatibility is the most common default: it means a consumer running the new schema can still correctly read messages produced under the old schema, which in practice means changes like adding an optional field with a default, or removing a field nobody required, are allowed, while removing a required field or changing a field's type outright is not. Forward compatibility is the less commonly needed but sometimes critical mirror image — a consumer still running the old schema needs to be able to read messages written under the new one, which matters when you can't guarantee every consumer upgrades before producers start writing the new shape. Full compatibility, requiring both, is the safest but most restrictive option, and the one to reach for on a topic with many independent consumer teams you can't coordinate a synchronized rollout with.

What interviewers look for: the registry framed as enforcement of a contract at deploy or build time rather than "a place schemas are stored," and correct, non-reversed definitions of backward versus forward compatibility, which candidates frequently mix up.

Q6 Compare Azure Event Grid, Event Hubs and Service Bus. When would you use each, and could you combine them in one architecture?#

Short answer: Event Grid is a push-based eventing backbone for discrete, reactive notifications — a blob was created, a resource changed — delivered to subscribers via webhooks with built-in retry and dead-lettering. Event Hubs is a high-throughput, partitioned event-streaming service built for ingesting large volumes of telemetry or event data, conceptually close to Kafka, with consumer groups reading independently from a retained log. Service Bus is a full enterprise message broker with queues and topics/subscriptions, ordered delivery via sessions, transactions and dead-lettering, built for reliable, often command-like messaging between applications rather than high-volume event streaming.

The distinguishing question is less "which is more powerful" and more "what shape of problem am I solving." Event Grid fits reactive, discrete event notification at low latency and high fan-out — triggering a function when a file lands in storage, notifying several unrelated subscribers that a resource changed — where each event is small, independent, and doesn't need to be replayed or processed in strict order relative to others. Event Hubs fits high-volume, ordered-within-a-partition ingestion where you need to retain and potentially reprocess a stream, or run multiple independent consumer groups over the same data, such as telemetry ingestion feeding both a real-time dashboard and a batch analytics pipeline simultaneously from the same stream. Service Bus fits scenarios that look more like traditional enterprise messaging than a firehose of events: a command that must be processed by exactly one consumer, an ordered sequence of operations for the same entity via sessions, or a workflow where the broker itself guarantees delivery with dead-lettering and retry policies baked in, rather than the consumer building all of that.

These aren't mutually exclusive, and combining them is a common real architecture: Event Grid can route a reactive notification that triggers a function which publishes a more detailed message onto a Service Bus queue for reliable, ordered processing, while a separate Event Hubs stream ingests high-volume telemetry from the same system for analytics, entirely decoupled from the operational messaging path. Choosing wrong usually shows up as a scaling or ordering mismatch later — using Service Bus for firehose-volume telemetry gets expensive and doesn't give you the replay and consumer-group model Event Hubs provides, and using Event Hubs for low-volume, latency-sensitive command dispatch adds partition and consumer-group complexity a service bus queue would have handled more simply.

What interviewers look for: choosing based on the shape of the problem — reactive notification, high-throughput streaming, or reliable enterprise messaging — rather than a memorized feature checklist, plus a believable example of combining more than one in the same architecture.

Common mistakes: treating the three as interchangeable "pick whichever" options, or assuming Event Hubs and Service Bus differ only in name rather than in their fundamental delivery and retention models.

Q7 How do you guarantee at-least-once event processing doesn't create duplicate side effects in a consumer?#

Short answer: You make the consumer idempotent: track which events have already been fully processed, keyed by the event's own ID or a natural business key, in the same transaction as whatever side effect the event triggers, so a redelivered event — which at-least-once delivery guarantees will eventually happen — either does nothing or produces exactly the same result as the first delivery.

Redelivery isn't a rare edge case in an event-driven system, it's a certainty over a long enough time horizon: a consumer can crash after processing an event's business effect but before committing its offset or acknowledgment, a broker can redeliver after a rebalance, and network retries can duplicate delivery at multiple layers. The naive fix, checking whether an event ID has been seen before as a separate step from doing the work, reopens a race: a crash between the check and the side effect leaves you exactly as exposed as having no check at all, which is why the check and the side effect need to be one atomic unit, typically a database transaction that both records the processed event ID and applies the business change.

Some side effects are naturally idempotent and need no extra tracking at all — setting a field to an absolute value produces the same end state no matter how many times it's applied, so double-processing is harmless by construction. Others are not: incrementing a counter, appending to a list, or calling a non-idempotent external API will visibly misbehave on redelivery unless the consumer explicitly deduplicates. The practical rule is to design the side effect to be naturally idempotent wherever the domain allows it, and fall back to explicit event-ID tracking only where it doesn't.

What interviewers look for: recognizing that redelivery is guaranteed, not hypothetical, in any at-least-once system, and the atomicity requirement between checking and acting, plus distinguishing naturally idempotent operations from ones that need explicit deduplication.

Q8 What is event storming, and how does it help a team discover bounded contexts and design an event-driven architecture before writing any code?#

Short answer: Event storming is a collaborative, workshop-based modeling technique where domain experts and engineers rapidly populate a timeline with sticky notes, each naming a domain event in past tense, then layer on commands, actors and external systems and, critically, the points of disagreement or ambiguity in language — which is usually where a bounded-context boundary actually is.

The technique deliberately starts with events rather than data models or class diagrams, because events are the thing domain experts can name accurately without engineering vocabulary getting in the way — "order placed," "payment declined," "shipment delayed" are statements a business stakeholder recognizes instantly, and getting the full timeline of what actually happens in the business, in order, surfaces gaps and misunderstandings between people who've never had to agree on a shared vocabulary before. Commands and the actors or policies that trigger them get layered on once the event timeline is roughly right, followed by external systems, read models and, often, explicit markers wherever two people in the room use the same word to mean genuinely different things.

That last part, disagreement in language, is the actual payoff for architecture, not just documentation. When the warehouse team's notion of "order" — a picking and packing unit tied to physical inventory — turns out to mean something structurally different from the billing team's notion of "order" — a financial transaction with line items and tax — that's not a naming inconsistency to smooth over, it's a strong signal you're looking at two separate bounded contexts that should almost certainly become two separate services publishing their own events, translated at the boundary rather than sharing one model that's secretly two different things wearing one name. Doing this exercise before writing code is far cheaper than discovering the same boundary mismatch months in, after two teams have built tightly coupled services around a shared model that never actually matched either team's mental model.

What interviewers look for: understanding event storming as a technique for discovering bounded-context boundaries through language disagreement, not merely "a workshop where you write sticky notes," and connecting it concretely to service boundaries and the events those services will publish.

Follow-up questions:

  • How would you run a lightweight version of event storming remotely, with a distributed team?
  • What's the difference between a domain event surfaced in event storming and the integration event a service eventually publishes externally?

Q9 How do you handle out-of-order and late-arriving events in a stream-processing pipeline?#

Short answer: You accept that event time — when something actually happened — and processing time — when your pipeline sees it — will diverge, and you design windowed aggregations around a watermark, a heuristic estimate of when you're unlikely to see any older events, that lets the pipeline decide when a time window is safe to finalize, while explicitly deciding what happens to stragglers that arrive after that point: drop them, emit a correction, or route them to a separate late-data path.

The naive approach, processing events in the order your pipeline happens to receive them and assuming that's chronological order, breaks the moment a producer's network hiccups, a consumer falls behind and catches up in a burst, or a client buffers events offline and uploads them together hours later. A windowed aggregation built on processing-time order alone will attribute a batch of late-arriving events to whatever window happens to be open when they finally show up, silently corrupting minutes that were already reported as final.

A watermark solves this by giving the pipeline an explicit, tunable notion of how long to wait: it's a moving threshold, based on observed event timestamps, below which the pipeline assumes it has seen everything that's coming, making it safe to close out an earlier window. Setting the watermark lag is a genuine trade-off: a short lag closes windows quickly, keeping the pipeline low-latency, but drops or misattributes more genuinely late data; a longer lag captures more late data correctly but delays every window's output and holds more state in memory while windows stay open. For data that arrives after the watermark has already passed, mature stream processors let you choose explicitly: discard it, emit a retraction and correction to whatever consumed the original, now-wrong, aggregate, or route it to a dedicated late-arrival path rather than silently corrupting a window that's already been reported as closed.

What interviewers look for: the distinction between event time and processing time stated explicitly, and a concrete mechanism, watermarks, with its actual trade-off, rather than "just sort by timestamp," which doesn't work when data can arrive arbitrarily late.

Q10 Design the eventing backbone for an order-fulfillment system spanning orders, inventory, shipping and notifications. Walk through your broker choice, topic design and failure handling.#

Short answer: Use one durable, ordered-per-key event backbone — Kafka or Event Hubs, keyed by order ID — as the record of what happened, with each bounded context owning its own topics and publishing thin-to-moderate domain events; each consuming context reacts independently and idempotently, failures route to a dead-letter path instead of blocking the stream, and a saga or process manager coordinates the multi-step workflow rather than any one service orchestrating the others directly.

Orders publishes OrderPlaced, and later OrderCancelled or OrderModified, to an orders topic partitioned by order ID, which guarantees every event for the same order is processed in order by a single consumer instance per context, without forcing ordering across unrelated orders that don't need it. Inventory subscribes, attempts a reservation, and publishes its own outcome, such as InventoryReserved or InventoryReservationFailed, to its own topic rather than writing back into the orders topic; this keeps each context owning and controlling the schema and cadence of its own events instead of multiple teams fighting over one shared topic's shape. Shipping and notifications each subscribe to whichever upstream events they care about, completely independently of each other, so adding a new consumer of an existing event never requires a change to the publisher.

Failure handling has to be explicit at each hop: a poison message that a consumer can't process after a bounded number of retries goes to a dead-letter topic instead of blocking that partition indefinitely for every order behind it, and dead-lettered messages get their own monitoring and a runbook, not silent disappearance. The harder failure case is business-level, not technical — inventory reservation fails for an order that's already been accepted — and that's not something individual services can resolve by retrying; it needs a saga or process manager explicitly watching the whole sequence from order placement to shipment, and on a failure partway through, publishing compensating events rather than leaving the order in a state no single service's local view can explain. Choosing not to build this coordination explicitly, and instead hoping the independent services figure it out through implicit event ordering, is the most common way this kind of architecture ends up with orders permanently stuck in an inconsistent state that nobody notices until a customer complains.

What interviewers look for: ownership of topics and schemas by bounded context rather than one shared topic everyone writes to, explicit dead-letter and poison-message handling, and recognizing that cross-service failure recovery needs an explicit saga, not implicit hope that independent consumers will self-correct.

Quick-Fire Round#

QuestionAnswer
Command or event: "ShipOrder"?Command — imperative, targeted, expects a specific action.
Thin or fat event: requires a callback to the source for details?Thin (event notification).
What does Kafka guarantee ordering within?A single partition, not the whole topic.
What happens when you add more consumers than partitions to a group?The extra consumers sit idle.
What compatibility mode lets new consumers read old data?Backward compatibility.
Which Azure service is closest to Kafka's model?Event Hubs.
Which Azure service provides ordered delivery via sessions?Service Bus.
What must be atomic in an idempotent event consumer?Recording the event ID and applying its side effect.
What does event storming use to find bounded-context boundaries?Points of disagreement in language ("hot spots").
What lets a stream processor safely close a time window?A watermark.

How to Prepare#

  • Practice distinguishing a genuine event from a disguised command by naming convention and intent, with a real example from a system you've built.
  • Be able to draw the event-notification-versus-event-carried-state-transfer trade-off and argue both sides for a specific scenario.
  • Know Kafka's per-partition ordering guarantee and the hot-partition failure mode cold; it's asked constantly.
  • Have the backward, forward and full compatibility definitions memorized without mixing them up.
  • Prepare a one-paragraph comparison of Event Grid, Event Hubs and Service Bus you can deliver without hedging.
  • Be ready to sketch a topic design — ownership per bounded context, partition key, dead-letter handling — for an unfamiliar domain on the spot.