If you remember five things
The whole concept in one glance. Everything below defends these five points.
- Log is truth. You store the facts that happened, not the current value. State is a function you compute by replaying the log.
- Writes are append-only. No in-place update, no lock on a hot row. Every write is a single sequential insert.
- Reads need projections. Replaying the log on every read is dead on arrival. You serve reads from a maintained read model and keep snapshots to cap replay.
- CQRS is the natural partner. The log is your write model, projections are your read models, and they scale on different axes.
- It is a specialist tool. Reach for it when history IS the product (ledgers, audit, compliance). Otherwise CRUD plus an audit table wins.
1The core idea#
Normal systems keep the answer. Event sourcing keeps the questions. That is the whole flip.
- CRUD keeps the current value. Your bank row says
balance = 420. Spend 50 and the system updates the row to370. The old number is gone forever. As far as the database is concerned, it never existed. - Event sourcing keeps the facts. You never store the balance. You store a list of things that happened:
AccountOpened,Deposited 500,Withdrew 80,Withdrew 50. The balance is something you compute by replaying that list. - State becomes a function of history. Replay the exact list on any machine and you get
500 - 80 - 50 = 370, every time.
The one line to anchor on:
- The log of events is the source of truth.
- Everything else, including the "current balance" on the screen, is a derived view you can throw away and rebuild.
Two properties fall out immediately, and interviewers love both:
- Nothing is ever lost. An update is just another event appended to the end. Ask "what was the balance on March 3rd at noon" and you replay events up to that timestamp. Try that with a plain
UPDATE. - Writes are append-only. You only ever add rows to the end of a log. No in-place mutation, no update lock on the hot row. The write is a single sequential insert, not a read-modify-write on a contended row.
State is a left fold over an immutable log of facts. If you can say that and mean it, the snapshots, projections, and CQRS all become obvious consequences rather than separate ideas to memorize.
The trade-off hits just as fast: reading the current state now costs you a replay. Section 2 deals with it.
2How it works#
Four moving parts, and the interview punishes you for blurring them: the command, the event, the event store, and the projection.
- Command. A request to change something: "transfer 50 dollars." A command can be rejected. The domain logic looks at current state, decides if it is valid, and emits events if so.
TransferMoneyis a command. - Event. A fact in the past tense. It already happened, so it cannot be rejected or deleted.
MoneyTransferredis an event. - Event store. An append-only log, ordered per entity (an "aggregate" in DDD terms, like one bank account). Appending is the only write operation.
- Projection (read model). Consumes the event stream and builds whatever shape your reads need: a balance table, a search index, a cache. Many projections can feed off the same events, each tuned for a different query.
Mixing "command" and "event" in the same breath, or writing an event called UpdateBalance. That is a tell you have read the blog posts but never shipped one. Commands are requests in the imperative; events are facts in the past tense. Say MoneyWithdrawn, never WithdrawMoney, when you mean the thing that already happened.
Why this pairs with CQRS.
- The separate write and read pipelines are exactly what CQRS (Command Query Responsibility Segregation) asks for.
- They are not the same thing, but they fit like a hand in a glove: CQRS says split your write model from your read model, event sourcing hands you a natural write model (the log) plus a natural way to feed any number of read models (projections off the stream).
The obvious problem: an account open for ten years has 40,000 events. You are not replaying all of them on every balance check. Two fixes:
- Snapshots. Every N events, write the computed state to the side. To get current state, load the latest snapshot and replay only the events after it. Replaying 50 events is cheap. Replaying 40,000 is not.
- Maintained projections. Keep the read model continuously up to date as events arrive, so a balance read is a normal indexed lookup. This is what most real systems do. The replay-from-scratch path is reserved for rebuilding a projection, not for serving live reads.
GET balance hits the read model, not the log. Truth and audit live in the log; speed lives in the projection.3A worked example with real numbers#
"Replay the log" stays abstract until you put a latency number on it. Say I am designing the balance service for a neobank with 10 million accounts.
The event stream for one account. Account acc_7781 has these events, each roughly 300 bytes on disk after serialization:
seq 1 AccountOpened balance effect +0
seq 2 Deposited 50000 (500.00) +50000
seq 3 Withdrew 8000 ( 80.00) -8000
seq 4 Withdrew 5000 ( 50.00) -5000
current balance = 37000 cents = 370.00
- I store money in integer cents, never floats.
50000 - 8000 - 5000 = 37000, which is 370 dollars.- Replaying 4 events takes microseconds.
Now scale it in time.
- This account is active for 10 years and averages 11 events a day (card swipes, interest postings, fees). That is roughly 40,000 events.
- Each replay reads 40,000 rows at 300 bytes each, so about 12 MB of I/O just to answer "what is the balance."
- At, say, 1,000 balance checks per second across hot accounts, naive replay is dead on arrival.
Fix it with a snapshot.
- I snapshot every 500 events. To read the balance I load snapshot number 80 (state after seq 40,000) and replay the 0 to 499 events since.
- Worst case replay is 499 events, about 150 KB, versus 12 MB. That is an 80x cut in read I/O, and it stays flat as history grows.
- The snapshot itself is one small row:
{account, seq: 40000, balance: 37000}.
Fix it better with a maintained projection.
- I keep a
balancestable updated as each event lands: one indexed row per account. GET balanceis a single primary-key lookup around 1 ms.- The 40,000-event log sits untouched behind it, used only for audit and for rebuilding the projection if it ever gets corrupted.
Reveal the answer
Sizing the whole system.
- 10M accounts times 40,000 events times 300 bytes is about 120 TB of event log over a decade.
- That sounds scary until you remember it is append-only and cold: 99% of it is never read.
- The hot read path is the
balancesprojection: 10M rows at maybe 80 bytes, under 1 GB, fits in memory on one box. - The shape of the whole trade: a large cheap immutable log plus small hot derived views. Say those numbers in an interview and you stop sounding theoretical.
4Trade-offs#
This is where interviews are won or lost. Anyone can describe the pattern. You get hired for knowing what it costs.
| Dimension | Event sourcing | Classic CRUD |
|---|---|---|
| Source of truth | Append-only event log | Current-state rows |
| Audit and history | Free, built in | Needs extra audit tables |
| Point-in-time state | Replay to any moment | Lost on every update |
| Read performance | Needs projections or snapshots | Direct, simple |
| Write model | Simple appends, no update locks | In-place updates |
| Schema change | Hard, old events are immutable | Run a migration |
| Operational complexity | High | Low |
| Debuggability | Replay the exact sequence | Guess from current state |
The three things you actually give up:
- Query complexity. You cannot
SELECT * FROM accounts WHERE balance > 1000against a log. There are no rows with balances in it. Every query you want has to be served by a projection you built ahead of time. If a product manager asks for a new report next week, you may need a new projection and a full replay to populate it. Against the 120 TB log, that replay is a real job you have to plan, not a one-liner. - Schema evolution of events. Events are immutable and they live forever. Ship
OrderPlacedwithpriceas an integer in cents, then three years later want it as a decimal with currency, and you cannot rewrite the old events. You carry versioned event types and "upcasters" that translate old shapes to new ones on read. This is the single most underestimated cost. Teams are usually fine for a year, then event versioning becomes a tax on every change. - Eventual consistency. The projection lags the log, even if only by milliseconds. A user transfers money, the event is committed, but the balance view has not updated yet when they refresh. For a bank that is a lawsuit. For a social feed it is invisible. Know which side of that line your system sits on.
Event sourcing
- History
- Is the product: ledgers, audit, compliance.
- Audit trail
- Free, it is the primary data.
- Point-in-time
- Replay to any moment in the past.
- Cost
- High operational complexity, event versioning tax, eventual consistency.
CRUD plus audit table
- History
- A side concern you log separately.
- Audit trail
- Needs a deliberate audit table.
- Point-in-time
- Only what you chose to snapshot.
- Cost
- Low. Solves 80% of what people think they need event sourcing for.
My honest take:
- Event sourcing is a specialist tool. Reach for it when history IS the product (ledgers, audit, compliance, anything where "why is it in this state" matters as much as the state).
- Do not reach for it because it sounds sophisticated.
- A plain CRUD table with a separate audit log solves 80 percent of what people think they need event sourcing for, at a fraction of the operational pain.
Saying no to your own pattern is the clearest senior signal in the room.
5Where it is used#
A few canonical fits, and what each one teaches:
- Financial ledgers. The canonical case. A ledger IS an event log by definition: you never edit a posted transaction, you post a correcting one. Accounting has worked this way for 500 years, so event sourcing is software catching up to double-entry bookkeeping.
- Order and fulfillment systems. An order moves through placed, paid, picked, shipped, delivered, returned. Each transition is a meaningful event that downstream systems (inventory, analytics, customer notifications) all want to react to. The event stream becomes the integration backbone.
- Version control. Git is event sourcing in disguise. Commits are immutable events, your working tree is a projection, and you can check out any point in history by replaying. If you understand Git, you already understand the pattern.
- Collaborative editing and distributed systems. When you merge changes from multiple sources or rebuild state on a new node, a stream of ordered events is far easier to reason about than reconciling two bags of mutable state.
The signal words that should make you reach for it:
- "audit," "regulatory," "undo/redo," "temporal query," or "replay for debugging."
- If the prompt is "design a URL shortener," event sourcing is the wrong answer and reaching for it shows poor judgment.
6Common pitfalls and gotchas#
The traps that separate someone who ran one of these from someone who read about it:
- Treating events as commands. New teams write events like
UpdateBalancethat carry the new value. That is a disguised CRUD update. A real event says what happened and why (MoneyWithdrawn 50), not what the row should become. Get this wrong and you lose the ability to re-derive state under new rules later. - Fat events that leak current state. If your event stores the resulting balance (
balance now 370) instead of the delta (withdrew 50), you have coupled the event forever to how you computed balance at that moment. Store the fact, derive the state. In the section 3 example, storingbalance: 37000inside theWithdrewevent would bake a bug in old balance math permanently into history. - No snapshot strategy. Day one, replays are instant because there are ten events. Eighteen months later a single aggregate has 100,000 events and your p99 latency quietly dies. Plan snapshots before you need them, not after the pager goes off.
- Ignoring idempotency on the read side. Projectors consume events, and in a distributed system they will occasionally see the same event twice (at-least-once delivery). If your projector does
balance = balance + amountwithout tracking which events it already applied, a redelivery double-counts that 50 and corrupts the view. Track the last processed event position per projection and skip anything at or below it. - Deleting data in a GDPR world. Immutable logs and "the user has a right to be forgotten" are in direct conflict. You cannot just delete events. The usual answer is crypto-shredding: encrypt personal data in events with a per-user key, then delete the key. The events remain but the PII is unrecoverable. Bring this up unprompted and interviewers notice.
- Rebuilding a projection takes hours. If a projection bug ships and you need to rebuild from a billion events, that replay can run for a long time. Teams that care keep projections rebuildable in parallel and partitioned by account, so a rebuild is minutes, not a weekend.
I throw the crypto-shredding question at candidates who sound too comfortable. If you name "immutable log versus right-to-be-forgotten" as a real tension and reach for per-user-key encryption plus key deletion before I prompt you, I mark you as someone who has operated one of these in a regulated shop. Reciting the happy path does not do that.
7How it shows up in interviews#
Interviewers probe event sourcing to see if you understand consistency, not just vocabulary. Here is what separates strong from weak, framed as the questions you will actually get.
"How do you read the current balance?"
"A user says the balance is wrong right after a transfer."
"How do you change an event's schema?"
"A projector sees the same event twice. What happens?"
"When would you NOT use this?"
The meta-move: mention CQRS and eventual consistency early, draw the split read/write diagram, and name snapshots and event versioning before you are asked. That tells the interviewer you have actually run one of these, not just read about it.
What you can now explain in the room
- Why the log is the source of truth and current state is just a derived view you can rebuild.
- Why reads need a maintained projection and snapshots instead of replaying the whole log.
- How event sourcing and CQRS fit together without being the same thing.
- The real numbers: a 120 TB cold log behind a sub-1 GB hot projection, and why that is fine.
- The three costs: query complexity, event schema evolution, and eventual consistency.
- When to say no to event sourcing and reach for CRUD plus an audit table instead.
Next, try the System Design Question walkthrough on designing a payment ledger, where these trade-offs stop being theory and you have to pick a side.
Get a prep plan built around your Event Sourcing interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.