CQRS and event sourcing are two of the most over-recommended patterns in software architecture, and senior interviewers know it — the strongest answers on this topic are as much about restraint as they are about mechanics. Expect to be pushed on when the added complexity actually pays for itself, how you keep a system's read side honest when it's structurally allowed to lag, and how you handle the unglamorous operational realities: an event schema that needs to change after millions of events already exist, a corrupted projection, or a GDPR erasure request against a log that's supposed to be immutable. This page works through both patterns with the trade-offs a .NET architect is expected to articulate, not just define.

Q1 What problem does CQRS actually solve, and what's a concrete sign that a system does not need it?#

Short answer: CQRS — Command Query Responsibility Segregation — solves the problem of a single model being a bad fit for both reading and writing when those two needs have genuinely diverged: the write side needs to enforce invariants and stay normalized, while the read side needs denormalized, query-shaped data that's fast to retrieve without joining across a dozen tables; the clear sign a system does not need it is when the read and write shapes are already basically the same, and the extra complexity of maintaining two synchronized models buys nothing over a single well-indexed model.

A reporting dashboard that needs a flattened, pre-joined view across orders, customers and products, updated within a few seconds, is a legitimate CQRS candidate — building that view as a live join on every request would be slow and would compete for the same database resources as the transactional write path, while a separate, denormalized read model sidesteps both problems. A straightforward internal CRUD tool, where the "read" is just the same row you wrote and the whole system serves a handful of concurrent users, gains nothing from splitting into two models — you'd be maintaining two schemas, a synchronization mechanism, and a mental model of eventual consistency for a workload that never needed any of it. The failure mode interviewers are checking for by name is architecture-first thinking: reaching for CQRS because it's a recognized pattern for "scalable" systems, rather than because a specific, measured pain — slow reporting queries competing with transactional writes, or a write model so normalized that building read views from it is genuinely expensive — already exists.

What interviewers look for: a specific, measurable trigger for adopting CQRS (query shape and write shape have diverged, or they contend for the same resources) rather than "it's more scalable," plus a real example of when to say no to it.

Q2 Does CQRS require event sourcing, or are they independent decisions?#

Short answer: They're independent decisions that are frequently bundled together in tutorials but don't require each other: CQRS is only about having separate paths (and optionally separate models) for reads and writes, which can be implemented with a single plain relational database and two different query strategies against it — a write path using EF Core with full change tracking, and a read path using raw SQL or Dapper against denormalized views or even the same tables — with no event store involved at all.

A common, simpler production pattern: one write DbContext that enforces invariants through your aggregate and domain model, and a completely separate set of read-only query handlers that run hand-written SQL or Dapper queries directly against the same database, sometimes against materialized views refreshed on a schedule, bypassing the write model's object graph entirely because the read side doesn't need it. This gets you CQRS's main benefit — the read path isn't constrained by the write model's shape, and can be optimized independently — without event sourcing's operational overhead: no event schema versioning, no replay logic, no separate storage technology to run. Event sourcing pairs well with CQRS because an event stream is a natural source to build projections from, but plenty of production CQRS systems just use two query paths against one normalized database, and event-sourced systems without any CQRS split also exist, though they're less common since an event stream is usually a poor shape to query directly.

What interviewers look for: stating plainly that the two are separable, with a concrete example of CQRS on a plain relational database — candidates who assume CQRS implies an event store are a common, easily-spotted gap.

Q3 Walk through building a projection from an event stream. What does "eventual consistency" actually mean here, and how do you communicate that trade-off to a product owner?#

Short answer: A projection is a subscriber that reads events from a stream in order — often via a catch-up subscription that starts from the last processed position — and applies each one to update a denormalized read model, such as a SQL table shaped exactly for a specific query; "eventual consistency" means there's a real, if usually small, window between when a command is accepted and when the projection reflects it, and communicating that honestly, rather than hiding it, is what prevents it from becoming a support incident.

C#
public class OrderSummaryProjector
{
    public async Task HandleAsync(OrderPlaced e, CancellationToken ct) =>
        await _readDb.InsertAsync(new OrderSummary(e.OrderId, e.CustomerId, e.Total), ct);

    public async Task HandleAsync(OrderShipped e, CancellationToken ct) =>
        await _readDb.UpdateStatusAsync(e.OrderId, "Shipped", ct);
}

The lag is typically milliseconds under normal load, but it's not zero, and treating it as zero is where teams get burned — a user submits an order, is immediately redirected to an order confirmation page that queries the read model, and occasionally sees a 404 or stale state because the projector hasn't caught up yet. The honest fix is designing around the lag rather than pretending it away: return the just-written data directly from the command's response instead of forcing a re-read from the read model immediately after a write, since the client already has the current state without needing the projection to catch up first. When you do need to communicate this to a product owner, frame it concretely — "after you place an order, the confirmation screen shows your order immediately from the write result; the order history list a few seconds later reflects it too" — rather than the abstract term "eventual consistency," which rarely lands as a design constraint until it's demonstrated as a real screen-by-screen behavior.

What interviewers look for: a concrete technique for hiding read-after-write lag from users (returning state from the command response) rather than just naming that lag exists, and a communication approach grounded in specific screens rather than jargon.

Q4 An event's shape needs to change after there are already millions of events of that type in the store. What are your options, and what is upcasting?#

Short answer: Because events are immutable and append-only by design, you cannot edit historical events in place without breaking the fundamental guarantee of the store, so the options are additive schema evolution with sensible defaults for missing fields, versioned event types with an upcaster that transforms an old-version event into the new shape at read time, or, as a last resort, a one-time migration that rewrites or copies an entire stream into a new one — expensive and risky, and avoided unless the first two genuinely don't apply.

Upcasting is the standard tool: when a projector or aggregate reads an OrderPlacedV1 event but the current code only understands OrderPlacedV2, an upcaster function transforms the stored V1 payload into the V2 shape in memory, on the fly, as part of deserialization — the stored bytes never change, only the in-memory representation the rest of the code sees does. This keeps consuming code simple, since handlers only ever need to reason about the current event version, while the upcaster (or a chain of them, for events that have gone through several revisions) absorbs the translation logic in one place. The tolerant-reader discipline that makes this sustainable is treating every event type's schema as append-only at the field level too — adding new optional fields with defaults rather than renaming or removing fields outright — so most changes don't need a new versioned event type at all, and upcasting is reserved for genuine structural changes.

What interviewers look for: upcasting explained correctly as a read-time transformation of old event data, not a rewrite of stored events, plus the additive-first discipline that minimizes how often a new versioned event type is even needed.

Follow-up questions:

  • How would you test an upcaster to make sure it correctly transforms every historical version still present in the store?

Q5 What's a snapshot in event sourcing, and when does an aggregate actually need one?#

Short answer: A snapshot is a periodically saved, serialized copy of an aggregate's state at a specific event version, used purely as a performance optimization so that loading the aggregate means reading the snapshot plus only the events after it, instead of replaying the entire stream from event one every time; an aggregate needs one when its stream has grown long enough, typically thousands of events, that replay latency becomes noticeable on the hot path, and doesn't need one at all when streams stay short, which describes most aggregates in most domains.

The trade-off is real and worth stating unprompted: a snapshot is extra storage, and it introduces its own versioning problem — if the aggregate's internal shape changes, old snapshots may no longer deserialize cleanly into the new shape, which means either versioning snapshots the same way you version events, or simply invalidating and regenerating them (a snapshot is disposable and rebuildable from the event stream, unlike the events themselves, which is exactly why it's safe to discard and regenerate one when in doubt). Because of this, the sensible default is not adding snapshotting on day one for every aggregate type — it's an optimization applied selectively, after profiling shows a specific aggregate's load time is actually a problem, typically for long-lived aggregates like a years-old customer account or a high-frequency trading position that accumulates thousands of events over its lifetime, not for an order that closes out after a dozen events.

What interviewers look for: treating snapshotting as an optional, profiling-driven optimization rather than a default part of "doing event sourcing correctly," plus awareness that snapshots themselves need a versioning or invalidation strategy.

Q6 A production incident corrupts a read-model projector, or you discover a bug in it. How do you recover, and why is this easier in event sourcing than in a traditional CRUD system?#

Short answer: Recovery is rebuilding: fix the projector's logic, then replay the entire event stream from the beginning through the corrected projector into a fresh read-model table or store, and cut traffic over once the rebuild catches up to the current stream position — this works because the event stream is the complete, durable source of truth, so any derived read model can always be regenerated from it, which is precisely the guarantee a traditional CRUD system's already-overwritten table can never offer.

In a CRUD system, if a bug corrupted a derived or denormalized column over time, there's no way to regenerate the correct historical values, because the inputs that produced each past state were never captured — only the current, already-corrupted state survives. In an event-sourced system, replaying is mechanical, if not instantaneous: stand up a new table, run the fixed projector against the full stream (or from a known-good snapshot forward), verify it against the live system, and swap it in, all without touching the event store itself, which was never the thing that was wrong. The operational concerns that come with this in practice are worth naming: replay time at genuine scale (millions of events can take real wall-clock time to reprocess, which may require running the rebuild in parallel with the old, still-serving read model rather than taking the system offline), and making sure the new projector's writes are idempotent so a partial rebuild that's restarted doesn't produce duplicate or inconsistent rows.

What interviewers look for: the specific reason this recovery path exists in event sourcing and not CRUD — the durable, replayable source of truth — plus practical awareness of replay time and idempotency as real constraints, not a hand-wave that rebuilding is instant or free.

Q7 How do you handle GDPR's right to erasure in an event-sourced system, where events are supposed to be immutable and append-only? Explain crypto-shredding.#

Short answer: Crypto-shredding resolves the direct conflict between "events are immutable and never deleted" and "a data subject has the right to have their personal data erased" by encrypting personal data fields per subject with a unique key stored outside the event store entirely, in a separate key management system; "erasing" the subject's data means permanently destroying their key, which renders the encrypted payload in every historical event mathematically unrecoverable forever, without ever mutating, deleting, or renumbering a single event in the log.

The event log itself stays completely intact — the sequence, the event types, and any non-personal facts remain exactly as they were, preserving the audit trail and stream integrity that made event sourcing attractive in the first place — only the specific encrypted fields become permanently unreadable once the key is gone. This requires deliberate design up front: personal data fields need to be identified and encrypted per subject at write time, aggregate and projector logic that needs to use that data while the subject is still active has to handle decryption (and has to be written so it degrades gracefully, rather than throwing, once a key is destroyed and the subject's history is being replayed for someone else's benefit, such as an aggregate rebuild), and the separate key store itself needs its own backup, rotation and access-control story, since losing keys prematurely is effectively an accidental mass-erasure event. It's also worth being explicit that this only protects fields actually identified as personal data and encrypted this way — crypto-shredding is a deliberate design decision made before data is written, not something retrofitted onto an existing event store after the fact.

What interviewers look for: correctly identifying the append-only-versus-erasure conflict as the core problem, and explaining crypto-shredding precisely — per-subject keys stored externally, destroyed to erase — rather than vaguely gesturing at "we encrypt the data."

Q8 What are the main operational concerns a team takes on by adopting event sourcing that a CRUD team doesn't have to think about?#

Short answer: Beyond the modeling changes, event sourcing adds a distinct operational surface: disciplined event versioning (covered above), storage that only grows since streams are never pruned or shrunk, requiring an archiving or snapshotting strategy over time; meaningfully longer projection-rebuild windows as data volume grows; specialized debugging tooling, since "what's the current state" now requires either a stream browser or a materialized projection rather than a simple row lookup; and a cultural shift where a bug in a command handler doesn't just produce a wrong value you can update — it produces a wrong but permanent historical event, so compensating events become a normal part of the domain vocabulary rather than an edge case.

That last point deserves emphasis because it surprises teams new to the pattern: you cannot "just fix" a bad event after the fact the way you'd run an UPDATE statement against a CRUD table — the correction has to itself be a new event, such as OrderQuantityCorrected, that's meaningful in the domain's own terms, which means the team needs to have actually designed for corrections as a first-class scenario, not an emergency improvisation. Testing style changes too: instead of asserting on final state, tests tend to follow a given-events, when-command, then-events shape, verifying that a specific sequence of prior events plus a new command produces the expected new event — a different discipline than typical CRUD unit tests, and one that needs to be taught explicitly to engineers joining the team.

What interviewers look for: naming several concrete, non-obvious operational costs — permanent-but-wrong events requiring compensating events, given-when-then testing style, storage growth — rather than only the commonly-cited "learning curve."

Q9 Compare eventual consistency handling strategies for CQRS: polling, a message broker, and a change-feed or outbox mechanism. When would you choose each?#

Short answer: Polling has the projector periodically query for new events or changes, which is the simplest to build and reason about but adds latency proportional to the poll interval and wastes work when nothing changed — reasonable for low-volume, batch-style projections where near-real-time freshness doesn't matter; a message broker such as Azure Service Bus or Kafka, typically via a library like MassTransit, gives near-real-time delivery across service boundaries but requires designing for at-least-once delivery, so consumers must be idempotent and dead-lettering must be handled; a change feed (Azure Cosmos DB's change feed) or an outbox table drained by a background service gives ordering and atomicity with the original write without needing a distributed transaction, and fits best for same-process or same-database projections rather than cross-service delivery.

The decision in a .NET stack usually comes down to two questions: does the projection need to cross a service or team boundary, and does the write already happen in a transactional store that supports an outbox or change feed. Within one bounded context, writing to a relational database via EF Core, an outbox table drained by a hosted background service is typically the right default — it reuses infrastructure you already have, guarantees the event isn't lost or duplicated relative to the write, and avoids standing up a broker for a projection that never leaves the process boundary. Across bounded contexts or services, a broker is usually necessary regardless, since two independently deployed services can't share a database transaction, and the broker's delivery guarantees (paired with idempotent consumers) are what make that safe. Polling remains a legitimate, low-effort choice specifically when the freshness requirement is genuinely loose, such as a nightly or hourly analytics rebuild, where building broker or change-feed infrastructure would be effort spent on a requirement that doesn't exist.

What interviewers look for: matching each mechanism to a specific scenario using the two deciding questions, rather than presenting all three as interchangeable with no selection criteria.

Q10 How would you explain to a skeptical team lead when the added complexity of full CQRS and event sourcing is worth it for a specific bounded context, not the whole system?#

Short answer: Frame it as a bounded-context-level decision, never an architecture-wide mandate: apply CQRS and event sourcing selectively to the specific contexts where their benefits map onto a real, named need — an audit-heavy domain like a financial ledger or medical record, where the event log itself is a business asset regulators or auditors actually want; a domain with genuine temporal query needs, such as "what did this account look like at close of business on a given date"; or a domain with real write/read shape divergence and scale — and leave every other bounded context in the same system on a plain CRUD model.

The pitch that tends to land with a skeptical lead is naming the specific pain each pattern removes, in that one context, rather than a general appeal to "scalability" or "best practice": for a ledger, it's that the event log is literally the audit trail a compliance team already needs, so event sourcing isn't extra work, it's building the audit requirement directly into the persistence model instead of bolting an audit table on separately; for a context with real read/write divergence, it's a specific slow query or a specific write-model normalization conflict that a denormalized read model measurably fixes. The corresponding honesty required is naming the bad candidates just as clearly — reference data, simple CRUD configuration screens, anything without a genuine audit, temporal, or scale need — so the team doesn't walk away assuming the pattern proved itself for the whole system rather than for the one context where it was actually justified. This mirrors the same discipline covered in Clean, Onion and Hexagonal Architecture Interview Questions: the architecture is a tool applied where its cost is justified, not a system-wide default.

What interviewers look for: a scoped, context-by-context pitch tied to specific, named pain points, plus the willingness to explicitly name bad-fit contexts in the same system — an answer that only argues in favor of the pattern, with no boundaries, reads as advocacy rather than architectural judgment.

Quick-Fire Round#

QuestionAnswer
What's the concrete trigger for adopting CQRS?Read and write shapes have genuinely diverged, or they contend for the same resources.
Does CQRS require event sourcing?No — CQRS can run on one plain relational database with two separate query paths.
What does an upcaster do?Transforms an old-version event into the current shape at read time, without rewriting stored data.
When does an aggregate need a snapshot?When its stream has grown long enough that full replay latency becomes noticeable — not by default.
Why is rebuilding a read model easier in event sourcing than CRUD?The event stream is a durable, replayable source of truth; a CRUD table's history is already gone.
What does crypto-shredding actually delete?A per-subject encryption key stored outside the event store — never the events themselves.
What delivery guarantee does an outbox or broker typically provide?At-least-once — consumers must be idempotent.
Should CQRS/event sourcing apply to a whole system or per context?Per bounded context, applied only where a specific audit, temporal or scale need justifies it.

How to Prepare#

  • Be ready to state a concrete trigger for CQRS and a concrete example of a system that doesn't need it — both sides of the question get asked.
  • Practice explaining that CQRS and event sourcing are independent decisions, with a plain-database CQRS example ready.
  • Know upcasting cold: it transforms data at read time and never rewrites what's stored — a frequent point of confusion.
  • Have the snapshot trade-off memorized, including that snapshots themselves need versioning or invalidation.
  • Rehearse crypto-shredding precisely — per-subject keys stored externally, destroyed to erase — since GDPR-versus-immutability is a favorite architect-level trap question.
  • Prepare a short, scoped pitch for when full event sourcing is worth it in one bounded context, and be equally ready to name the contexts where it isn't.