The Principle
Design for today’s scale with a named path to tomorrow’s — not for an imagined future. Every famous large system passed through a boring stage: Facebook was one PHP monolith on MySQL; Amazon started as a single service; Instagram famously served millions of users on Django + Postgres with a tiny team.
The skill being tested in design interviews is not “draw the final billion-user architecture.” It is: start at the correct simple point, then evolve it step-by-step under pressure, naming the trigger that forces each step.
The Canonical Evolution Path
STAGE 1: BASELINE STAGE 2: READS GROW
┌────────┐ ┌─────┐ ┌────────┐ ┌────────┐ ┌─────┐ ┌────────┐
│ Client │──►│ App │──►│ Postgres│ │ Client │─►│ CDN │─►│App × N │
└────────┘ └─────┘ └────────┘ │ │ └─────┘ │ ┌────┐│
└────────┘ │─►│ LB ││
works to ~10k users │ └────┘│
└───┬────┘
reads hit DB hard
│
STAGE 4: WRITE SCALE ◄────────────────── STAGE 3: ADD CACHE/REPLICAS
┌──────────┐ App × N → Redis cache (hot reads)
│ Sharded │ → read replicas
│ Postgres │ trigger: single writer trigger: cache misses +
└────▲─────┘ saturates, replicas replica lag visible
│ can't help writes
Queue buffers heavy writes,
async workers smooth spikes
Each arrow has a trigger and an action. That pairing — not the final diagram — is the design knowledge.
Trigger → Action Table
| Trigger (evidence, not vibes) | Action |
|---|---|
| DB CPU high on SELECTs; static assets dominate bandwidth | Add CDN |
| Repeated identical queries; DB still bottleneck after indexes | Add cache layer |
| Read load saturates primary | Add read replicas (watch replication lag) |
| Single writer near saturation; replicas can’t help writes | Shard or move hot tables to write-optimized store |
| Request timeouts during traffic spikes; users blocked on slow work | Queue + async workers |
| One service’s deploy cadence blocks others; teams collide | Extract that service |
Why Not Start at Stage 4?
The end-state architecture carries permanent taxes:
- Sharding from day one: no data to shard, but every query now routes through partition logic; resharding later would be cheaper than living with premature shards.
- Microservices day one: distributed tracing, eventual consistency, and network debugging before product-market fit proves the product deserves them.
- Kafka + Flink + K8s + multi-region: operational surface exceeds team size; the system manages the engineers instead of vice versa.
Complexity added before evidence arrives is complexity that never leaves.
What “Evolutionary” Design Requires Up Front
Simple does not mean naive. Two cheap early decisions keep evolution possible:
- Stateless app tier — horizontal scaling later must be trivially possible.
- One clear write path per data type — sharding later needs a single owner to split.
Conversely, some choices actively block evolution: business logic inside stored procedures, in-process sessions, direct cross-table joins everywhere.
Interview Framing
The highest-signal interview pattern: draw the boring baseline, state its capacity (“this handles roughly X”), then walk the trigger→action chain as the interviewer raises load. Answering “we’d start with microservices and Cassandra” without triggers reads as memorized end-states — the opposite of design judgment.
Premium Content
Unlock Start Simple and Evolve and all premium lessons with a subscription.
From ₹199.99/year — See plans