System Design Trade-offs: A Cheat Sheet
The 5 core trade-offs every system design interview (and production decision) comes down to. Swipe through each one.
1. Consistency vs. Availability
In a network partition, you cannot have both.
- Choose consistency (CP): Banking, inventory, anything where stale reads cause real damage.
- Choose availability (AP): Social feeds, view counts, activity logs — eventual consistency is fine.
CAP theorem isn't a gotcha — it's a design decision. Make it explicit.
2. Latency vs. Throughput
Optimizing for one often hurts the other.
| Goal | Pattern |
|---|---|
| Low latency | Small batches, avoid buffering |
| High throughput | Large batches, pipelining, async |
A Kafka consumer with fetch.min.bytes=1 has low latency. With fetch.min.bytes=1MB it has high throughput. Pick based on your SLA.
3. Read vs. Write Optimization
Your database can't be equally fast at both under load.
- Read-heavy: Add read replicas, caching (Redis), denormalize
- Write-heavy: Event sourcing, write-ahead logs, async processing
Most CRUD apps are read-heavy (10:1 reads to writes). Design for reads first.
4. Monolith vs. Microservices
Neither is universally better.
| Monolith | Microservices |
|---|---|
| Simpler ops, easier debugging | Independent deployments |
| Hard to scale specific parts | Isolated fault domains |
| Best for teams < 10 | Best when teams own services |
The real trade-off: organizational complexity mirrors system complexity (Conway's Law).
5. Push vs. Pull
How data flows to consumers matters at scale.
- Push (fan-out): Low latency, but sender controls load. Works well for chat, notifications.
- Pull (polling / Kafka): Consumer controls rate. Better for batch pipelines and uneven consumers.
Most systems mix both. Use push for real-time UX, pull for data processing.