If you remember five things
The whole concept in one glance. Everything below defends these five calls.
- Split the models. Commands (writes) and queries (reads) stop sharing one model. The write side guards invariants; the read side is shaped exactly for how it is consumed.
- It is a spectrum, not a switch. CQRS can be two code paths on one database all the way up to split stores fed by a change stream. Do not confuse it with event sourcing or two databases.
- You pay in staleness. Reads lag writes. The whole design question is how much lag you tolerate and where you refuse to.
- Justify it with a ratio. Reach for it when reads dwarf writes (roughly 10:1 or more) and the read shape differs from the write schema, not because it sounds advanced.
- Projectors must be boring. Idempotent, lag-monitored, and replayable. That is the operational cost you take on, and the interviewer wants to hear you name it.
1The core idea#
Strip the jargon and CQRS (Command Query Responsibility Segregation) says one thing: stop forcing your writes and your reads to use the same model.
In plain CRUD, one model does everything.
- The same
Orderobject andorderstable handle "place an order," "show my order history," and "show the ops dashboard." - That works fine until the jobs start fighting.
- Writes want normalized tables and strong invariants. Reads want denormalized, pre-joined, pre-aggregated views.
- At scale a six-way join on every request is the gap between a 20ms p99 and a 400ms p99, and the read path is the one your customers feel.
CQRS cuts the system into two halves:
- The command side. Handles writes. A command is an intent:
PlaceOrder,CancelReservation,ApplyDiscount. It validates business rules, enforces invariants, and changes state. Commands return success or failure, not data. - The query side. Handles reads. It serves one or more read models shaped exactly for how the UI or API consumes them. Queries never change state.
The mental model I give juniors: a busy restaurant.
- The kitchen (commands) cares about correctness, order, and not burning anything.
- The display board (queries) cares about being fast and easy to read.
- You never make every customer walk into the kitchen to check on their food. You post a status board, and you accept it is a few seconds behind the stove.
- That lag is the whole deal with CQRS, and it is the first thing a sharp interviewer presses on.
CQRS does not require two databases, does not require event sourcing, and does not require message queues. Those are common companions, not the definition. At its simplest, CQRS is two code paths against the same database, one tuned for writes and one for reads.
2How it works#
Here is the version you will usually draw on a whiteboard: a write store, a read store, and something that keeps the read side in sync.
The flow, step by step:
- A client sends a command.
- The command handler validates it and commits to the write store, the system of record.
- That commit emits a change: an event, a change-data-capture row, or a message on a bus.
- A projector (sometimes called a denormalizer) consumes the change and updates one or more read models.
- Queries hit the read models directly and never touch the write store.
The sync in the middle is where the design decisions live. Three ways to wire it:
- Synchronous, same transaction. Update the write table and the read view in one transaction. Simple and strongly consistent, but now write latency includes read-model maintenance and you lose the independent scaling that made CQRS attractive. Call it "CQRS lite."
- Change data capture. Tail the write database's log (Postgres logical replication, MySQL binlog, DynamoDB streams) and project from there. No application code to emit events, and you catch every change, including ones that bypass your API.
- Event-driven. The command side publishes domain events to Kafka or a similar bus, and projectors subscribe. This pairs naturally with event sourcing but does not require it.
The timing that trips people up:
Drawing the two boxes and treating the sync arrow as a detail. The arrow is the interview. If you cannot say which mechanism you picked (same transaction, CDC, or events) and why, you have shown the pattern without showing you have run it.
3A worked example with real numbers#
Theory is cheap. Sizing a concrete system is the move that separates a passing answer from a hand-wave in the loop.
The system: an e-commerce product catalog at a mid-size marketplace. Numbers I would put on the board:
- Read traffic: 50,000 product-page views per second at peak, roughly 4.3 billion reads per day.
- Write traffic: 500 price or inventory updates per second at peak, mostly from seller tools and inventory syncs, about 43 million writes per day.
- Read-to-write ratio: 100 to 1.
- A single product page needs data from 7 tables: product, pricing, inventory, seller, reviews-summary, shipping-options, and category-breadcrumb.
In plain CRUD, each page view runs that 7-way join.
- Say each join hits an index and lands in 40ms warm, 220ms cold.
- At 50,000 reads per second, the join work alone would need north of 2,000 database cores served straight from the normalized write store.
- That is absurd, and it couples read scaling to write scaling.
Now CQRS.
- Keep the normalized write store: 7 tables, strong invariants (a seller cannot set negative inventory).
- On every committed change, capture the row via Postgres logical replication.
- Run a projector that writes one flat denormalized document per product into a read store (DynamoDB or a document cache).
- The read store holds about 20 million product documents at roughly 4KB each: 80GB, trivially frontable with Redis.
The payoff in numbers:
- A product-page read is now a single key lookup,
GET product:{id}. p50 drops to 3ms, p99 to 12ms, versus the old 40ms to 220ms. - The read store scales on the read curve (shard by product id, add replicas). The write store stays small, seeing only 500 writes per second, which one primary handles comfortably.
- Projection volume is 500 events per second, trivial for one Kafka partition set and a handful of projector workers.
Reveal the answer
And the price, stated honestly:
- When a seller changes a price, the read model lags. You measure that lag.
- With logical replication plus a projector, the staleness budget is under 2 seconds at p99, under 500ms at p50.
- For a catalog that is fine. A price 1 second stale on a browse page is a non-event.
- At checkout, re-read the authoritative write store so the customer is never charged a stale price. That one sentence, naming where you refuse to tolerate staleness, is what reads as senior.
4Trade-offs#
This is the section interviews actually reward. CQRS buys you real things and charges you real things.
The upside
- Independent scaling
- Read models, caches, and replicas scale on the read curve; the write side scales on its own.
- Query-shaped reads
- No runtime joins. A product page reads one document; a report is pre-aggregated.
- Cleaner writes
- The command side drops presentation concerns. Invariants live in one place.
- Many views, one source
- One order stream feeds a customer view, a fraud model, and a warehouse export.
The downside
- Eventual consistency
- Reads lag writes by default. Read-your-own-writes is no longer free.
- More moving parts
- Projectors, a change stream, replay tooling, lag monitoring. More to break at 3am.
- Duplicated data
- The same fact lives in the write store and in N read models. Rebuilds and migrations get harder.
- Harder debugging
- "The dashboard is wrong" now means "which stage: write, event, projector, or read model?"
A quick comparison I use when deciding where on the spectrum to land:
| Dimension | Plain CRUD | CQRS lite (one DB) | Full CQRS (split stores) |
|---|---|---|---|
| Read latency | ties to write schema | tunable | lowest, purpose-built |
| Consistency | strong | strong | eventual |
| Operational cost | low | medium | high |
| Write throughput ceiling | shared with reads | shared | independent |
| Good fit when | read and write shapes match | one DB, reads need tuning | read load dwarfs writes |
My honest opinion after shipping a few of these:
- Start at CRUD.
- Move to CQRS lite (separate read and write code paths, maybe a read replica) when read queries start hurting.
- Go full split-store only when you have a measured reason.
- Jumping straight to event-sourced CQRS on a greenfield service is the most common way I have seen teams bury themselves in accidental complexity.
5Where it is used#
CQRS shows up anywhere the read and write worlds genuinely diverge.
- E-commerce catalogs. Writes are rare (a seller updates a price), reads are enormous (millions of product-page views). The page reads a flat denormalized document built from a dozen upstream tables. The exact case sized above.
- Order and payment systems. The write side enforces strong invariants (you cannot ship what is not paid). Separate read models serve the customer order view, the finance reconciliation report, and the ops dashboard, each totally different.
- Social feeds and activity streams. Writing a post is one small event. Reading a feed is a heavily precomputed, fanned-out read model. The timeline you scroll is a read projection, not a live join across who-follows-whom.
- Collaboration and audit-heavy tools. Paired with event sourcing, the command log becomes a full audit trail for free, and read models are just replays of that log into useful shapes.
When I reach for it in an interview answer: when I can point at a specific pressure.
- Read-to-write ratio above roughly 10 to 1.
- Read queries that need a shape the write schema cannot serve cheaply.
- A need to feed several very different consumers from one source of truth.
If none of those are true, I say so, and I do not add CQRS just to sound advanced. That restraint scores points.
6Common pitfalls and gotchas#
Treating eventual consistency as a bug instead of a contract. The number one failure.
- A user updates their profile, the API returns 200, they refresh, and the old value shows because the read model is 300ms behind.
- Fix one: read-your-own-writes, routing a user's own reads to the write store briefly.
- Fix two: return the new state in the command response so the UI updates optimistically without re-reading.
- Decide this on purpose. Do not discover it in production.
Unbounded projection lag. Projectors fall behind during spikes or after a deploy.
- Nobody notices until the data is minutes stale.
- Monitor lag as a first-class metric and alert on it.
- Treat projection lag like replication lag, and set the alert at the staleness budget you promised (2 seconds in the catalog example).
Non-idempotent projectors. Event delivery is usually at-least-once.
- Your projector will see the same event twice.
- If "add $10" runs twice, the balance is wrong.
- Projections must be idempotent, keyed on an event ID or version, so a replay is safe.
No replay story. You will eventually need to rebuild a read model from scratch.
- A projector bug corrupted it, or you want a new view.
- If you cannot replay the source of truth into a fresh read model, you are stuck.
- Design the rebuild path before you need it.
Leaking writes through the read side. Someone adds a tiny update inside a query handler "just this once."
- Now the read model is also a write path, and your consistency guarantees are gone.
- Keep the wall clean.
Overusing it. CQRS on a service with 50 writes a day and matching read shapes is pure overhead.
- The gotcha is cultural: resist adding it because it is in the architecture deck.
7How it shows up in interviews#
Interviewers rarely ask "what is CQRS." They hand you a design problem (a ticketing system, a feed, an order service) and watch whether you introduce CQRS at the right moment for the right reason.
What they probe:
- Do you justify it with numbers? Weak: "We'll use CQRS for scale." Strong: "Reads are about 50x writes and the product page needs a denormalized view, so I split the read model and feed it via change data capture, accepting roughly sub-second staleness."
- How do you handle the consistency gap? They want you to name read-your-own-writes and propose a concrete fix, not wave it away.
- How do projections stay correct? Idempotency, ordering, lag monitoring, and replay. Mentioning replay unprompted signals you have operated one of these.
- Do you know when NOT to use it? "Here CRUD is enough, the read and write shapes match and traffic is low" is often the strongest answer in the room.
- Can you separate CQRS from event sourcing? Many candidates fuse them. Saying "I'm using CQRS here but a simple projection off CDC, not full event sourcing, because I don't need the audit log" shows real depth.
Here is the contrast I want you to hear in your own answer, using the catalog from section 3.
Candidate (weak): Product pages are slow, so I'll add CQRS. We'll have a write database and a read database, and the read one will be fast. That handles scale.
Candidate (strong): Reads are 50,000 per second against 500 writes, a 100 to 1 ratio, and a page needs a 7-table join. I keep the normalized Postgres write store for invariants, capture changes with logical replication, and project a flat per-product document into a read store fronted by Redis. That turns a 40ms join into a 3ms key lookup. I accept eventual consistency with a p99 staleness budget under 2 seconds, which I monitor and alert on. Browse pages tolerate that, but at checkout I re-read the write store so nobody is charged a stale price. Projectors are idempotent keyed on event version, and I have a replay path to rebuild the read model from the write log if a projection bug corrupts it.
The weak answer names the pattern and nothing else: no numbers, no sync mechanism, no staleness budget, no mention of what breaks. The strong answer sizes every claim, names every trade, and has the failure modes already handled. That is the difference between someone who read about CQRS and someone who has been paged by it.
A weak answer bolts CQRS onto everything and hand-waves the lag. A strong answer treats it as a deliberate trade: name the pressure, pick the sync, state the staleness budget in real units, and show you can monitor and rebuild it.
"A user updates their profile and immediately sees the old value. How do you fix that?"
"Your projector sees the same event twice. What happens to the data?"
"A projection bug corrupted the read model. How do you recover?"
"Is this CQRS or event sourcing? Aren't they the same?"
"How do you keep the read model from falling minutes behind without anyone noticing?"
What you can now explain in the room
- Why forcing reads and writes through one model breaks down, and what the split actually buys.
- That CQRS is a spectrum from two code paths on one DB up to split stores, and that it is not event sourcing.
- How to size the decision: a 100:1 read-to-write ratio and a 7-table join turning a 40ms read into a 3ms lookup.
- Where you tolerate staleness (browse) and where you refuse it (checkout re-reads the write store).
- The three sync mechanisms (same transaction, CDC, event bus) and when each fits.
- Why projectors must be idempotent, lag-monitored, and replayable, and how you recover a corrupted read model.
For a full worked example, see the System Design Question walkthrough on designing a high-traffic e-commerce product catalog, where the read-write split drives the whole design.
Get a prep plan built around your CQRS interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.