How it works
With ordinary CRUD, a service updates a current-state record, such as changing an account balance from $100 to $70. With event sourcing, it appends a fact such as FundsWithdrawn with an amount of $30; the event history is the source of truth, not an optional audit trail beside the balance.
A command requests a change; an event records an accepted fact. For WithdrawFunds, the service loads the account's state, checks available funds, and appends FundsWithdrawn only if the command is valid. Rejected requests do not become successful withdrawal events.
Events usually belong to a stream for one business entity, often called an aggregate, such as an account or order. Each event carries a stream ID, sequence number, event ID, type, and schema version. An atomic append with an expected stream version prevents two writers from both accepting changes against the same old state; it does not provide transactions across unrelated streams.
A deterministic reducer applies events in sequence to reconstruct state. A snapshot stores state through a known sequence number, so loading an account can start from that snapshot and apply only later events. Snapshots accelerate recovery but do not replace the authoritative history.
Read models, also called projections, turn events into query-friendly tables or indexes. A worker might update an account summary and a transaction-history view from the same stream. Asynchronous projections can lag, so balance-sensitive commands must use authoritative state rather than a stale display table.
Projection workers track progress and handle redelivery without applying an event twice. Rebuilding a view replays historical events without repeating external actions such as charging a card or sending an email. An event store can be implemented in a transactional database; using a message broker alone does not establish event sourcing.
A worked example
Two withdrawals against the same account
An account stream has version 12 and an available balance of $100. Two requests arrive: withdraw $70 and withdraw $50. Both initially load version 12. The first appends FundsWithdrawn($70) with expected version 12, creating version 13. The second append fails its version check; after reloading the $30 balance, the service rejects the $50 withdrawal. A retried first request carries the same command ID, so durable deduplication returns its prior result rather than withdrawing again. The balance projection may briefly still show $100, but it never authorizes withdrawals. A snapshot at version 12 plus event 13 reconstructs $30. A real money system must also preserve balanced ledger entries within its transaction boundary.
Trade-offs to say out loud
- Reconstructable history versus retention cost
- You can explain changes and rebuild state, but must retain, back up, secure, and eventually migrate the authoritative event history.
- Flexible read models versus projection operations
- Multiple views can serve different queries, but each needs progress tracking, duplicate handling, lag monitoring, and a rebuild procedure.
- Concurrency safety versus hot-stream limits
- Expected-version appends protect one stream's invariants, but a heavily contended stream serializes writes and cross-stream invariants need additional coordination.
- Replay versus long-term compatibility
- Old events must remain interpretable through versioned handlers or transformations, and replay must not depend on today's clock, configuration, or external responses.
- Immutable facts versus corrections and deletion
- Business corrections usually append compensating events, while personal-data deletion requires an explicit design such as keeping sensitive fields outside the event history.
- Recoverability versus engineering complexity
- Snapshots, projection cutovers, replay isolation, and command deduplication add work that ordinary CRUD may avoid.
When to reach for it
- The requirements ask what happened, in what order, and why a business entity reached its current state, not just what its current fields contain.
- Historical facts must support new read models, reconciliation, or reconstruction after a projection bug.
- A payment, trading, or order workflow has meaningful state transitions whose history must survive retries and failures.
- A document system needs a durable sequence of accepted edits for reconnect and replay, alongside an explicit concurrency model.
- Prefer CRUD when current-state queries dominate and transactional updates plus an audit table satisfy the history requirements.
How to bring it up in the interview
- Start with the requirement that earns the complexity: explain exactly which history must be authoritative and why CRUD plus an audit trail is insufficient.
- Draw the write path separately from projections: command validation, expected-version append, durable acknowledgment, and asynchronous view updates. Expect questions about concurrent commands and ambiguous append outcomes.
- Name the consistency boundary. Explain which invariants fit inside one stream and how multi-account or multi-entity changes remain atomic; event sourcing alone does not solve distributed transactions.
- Walk through recovery: restore a snapshot, replay the tail, rebuild a new projection, verify it, and switch readers. Expect probes about schema evolution, lag, and avoiding repeated side effects.
- Separate event sourcing from related tools: CQRS separates read and write models, a broker transports messages, and an outbox reliably publishes committed changes. None requires event sourcing.
Common mistakes
- Calling any Kafka topic, change-data-capture feed, or audit table event sourcing even though mutable database rows remain the source of truth.
- Assuming append-only storage automatically prevents overspending, duplicate commands, or conflicts between concurrent writers.
- Using a lagging read projection to authorize a withdrawal or another invariant-sensitive operation.
- Replaying historical events through handlers that resend notifications, call payment providers, or recompute past decisions using current external data.
- Treating event sourcing as a substitute for double-entry accounting, reconciliation, or a privacy and retention policy.
Where it comes up
Interview questions where event sourcing is part of a strong answer, and why.
- Design a payment systemAuthoritative financial history and rebuildable balance views make the distinction between event history, projections, and ledger invariants central.
- Design a stock trading app like RobinhoodAccepted order transitions and execution facts need durable history to reconstruct state and reconcile uncertain execution outcomes.
- Design a wire transfer systemAn immutable transfer history supports reconstruction and reconciliation while exposing the need for atomic balanced entries beyond event append alone.
- Design Google DocsA durable history of accepted document operations enables reconnect replay and snapshot-based recovery, with OT or CRDT rules separately handling concurrent edits.
Questions people ask
What is event sourcing vs CRUD?
CRUD usually treats current database records as authoritative and updates them in place. Event sourcing treats the ordered history of accepted events as authoritative and derives current state from it. CRUD can still preserve an audit trail; the key distinction is whether that trail is sufficient and authoritative for rebuilding business state.
Does event sourcing require CQRS or Kafka?
No. CQRS separates command and query models, while event sourcing defines how authoritative state is stored; they are often combined but are independent choices. Kafka can distribute events, but its retention, ordering, and concurrency semantics must fit the design before it can serve as authoritative storage.
How do you handle mistakes in event sourcing?
For a business mistake, append a corrective fact, such as a refund or reservation release, rather than rewriting the original event. For a projection bug, fix the projection code and rebuild the view from history. Incorrect historical event data may require a controlled repair or migration; replay alone cannot make bad facts correct.