Overview
CQRS (Command Query Responsibility Segregation) separates the model that changes data (commands) from the model that reads data (queries). Writes go to a normalized store optimized for validation and consistency; reads come from one or more denormalized read models optimized for each screen or query, updated from events emitted by the write side.
CQRS lets reads and writes scale and evolve independently, and makes complex query needs (search, dashboards, feeds) simple without compromising the write model. The cost is eventual consistency between write and read models, plus more infrastructure to keep projections in sync.
The kitchen (write side) follows strict recipes and inventory rules. The menu board and wait-time display (read side) are simplified views updated whenever the kitchen changes something. Customers read the board, not the kitchen's inventory ledger.
When to use it
- Read and write workloads differ greatly in shape or scale.
- Many different views of the same data (search, dashboards, mobile).
- Complex domains with rich write-side business rules.
- Combined with event sourcing.
Where it shows up in interviews
Recognize it when: search, feeds, or dashboards over transactional data.
- Design product search
- Design an order history page at scale
Recognize it when: reads vastly outnumber writes.
- Design a social feed
- Design a banking statement view
Where it is used in real software
Many e-commerce systems write to PostgreSQL and project into Elasticsearch for search: a common CQRS form.
Frameworks and databases built around CQRS and event sourcing.
Timelines and dashboards precomputed from event streams are read models.
Key terms
- Command
- Intent to change state (PlaceOrder).
- Query
- Request for data with no side effects.
- Write model
- Enforces invariants; source of truth.
- Read model / projection
- Denormalized view built from events.
- Projection lag
- Delay before reads reflect a write.
How it works, step by step
- 1Command arrives
Validate against the write model's rules.
- 2Write model commits and emits events
OrderPlaced, via outbox or event store.
- 3Projectors consume events
Update read models: search index, dashboard table, cache.
- 4Queries hit read models
Fast, shaped for each screen.
- 5Handle lag
Optimistic UI or return data from the command response.
STEP 1The client sends PlaceOrder. The command handler validates stock and rules.
One write model, several read models
E-commerce orders
| Read model | Store | Shape | Used by |
|---|---|---|---|
| Order history | DynamoDB | Orders by customer, newest first | Mobile app |
| Product search | Elasticsearch | Products with stock and ratings | Search page |
| Sales dashboard | ClickHouse | Revenue by hour and region | Analysts |
| Order details | Redis cache | Full order JSON | Support tool |
NOWRead model: Order history | Store: DynamoDB | Shape: Orders by customer, newest first | Used by: Mobile app
Each read model is disposable: it can be rebuilt by replaying events if its shape changes.
Implementation
// Command side: enforces rules, writes the source of truth, emits an eventexport async function placeOrder(cmd: { customerId: string; items: Item[] }) { return db.transaction(async (tx) => { await reserveStock(tx, cmd.items); // invariant: no overselling const order = await tx.orders.insert({ customerId: cmd.customerId, items: cmd.items, status: "placed" }); await tx.outbox.insert({ type: "OrderPlaced", payload: order }); return order; });} // Projector: builds a read model optimized for "my orders" screenexport async function onOrderPlaced(event: { payload: Order; eventId: string }) { const { id, customerId, items, createdAt } = event.payload; await readDb.put({ pk: `customer#${customerId}`, sk: `order#${createdAt}#${id}`, summary: { id, itemCount: items.length, total: items.reduce((s, i) => s + i.priceCents * i.qty, 0) }, }, { idempotencyKey: event.eventId });}Complexity and performance
Precomputed view.
Monitor it.
Trade-offs
Tailored read models are fast and flexible, but you now maintain event pipelines and multiple stores.
Users may not see their change immediately in read views; design the UX for it.
Variants and related techniques
Separate read and write code paths and views within one database.
Events are the write model; projections are built from the event store.
Common mistakes
- Applying CQRS to simple CRUD.
Fix: Use it only where read and write needs truly diverge.
- Non-idempotent projectors.
Fix: Events can be redelivered; dedupe by event ID or use upserts.
Interview questions
What problem does CQRS solve?
It separates the write model, which enforces business rules, from read models tailored to queries, so each can be optimized and scaled independently, avoiding one schema that is bad at both.
How do you handle a user not seeing their order right after placing it?
Return the created order in the command response and render it optimistically, route that user's read to the write model briefly, or wait until the projection reaches the event's position.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Split an order service into command and query sides | Medium | Projections. |
| Design search and dashboards over an order system | Hard | Multiple read models. |