System Design · Lesson 5
How to Solve Design Problems
Whether the prompt is “design a notification service” or “design a large API,” a consistent decision process is more valuable than memorizing one architecture.
Step 1: define the product boundary
Choose the two or three core use cases and explicitly defer secondary features. Clarify who uses the system, expected latency, availability, data retention and security constraints.
Step 2: estimate scale
Approximate requests per second, read/write ratio, object size and storage growth. Use round numbers. The purpose is to identify orders of magnitude, not to produce accounting-grade forecasts.
Step 3: define APIs and data
Sketch endpoints or event contracts and the major entities. Ask which operations require transactions, uniqueness, search, ordering or high write throughput. These access patterns drive storage choices.
Step 4: build the simplest path
Start with clients, load balancing, stateless services and a database. Then add caches, queues, replicas, object storage, search indexes or separate services only when a requirement creates a reason.
Step 5: follow one request end to end
Walking through a read and a write reveals missing components, authentication decisions, latency risks and consistency assumptions. For asynchronous work, describe what happens before and after the queue.
Step 6: attack the bottlenecks
Discuss database hot spots, large fan-out, cache stampedes, retry storms, slow dependencies and skewed traffic. Explain the mitigation and the new complexity it introduces.
Step 7: cover production concerns
Add timeouts, idempotency, observability, rate limiting, access control, backups and disaster recovery at the level appropriate to the prompt. Avoid listing technologies without explaining why they are needed.
Example: notification service
A basic design might accept a notification request through an API, persist an idempotency key, publish work to a queue, have channel-specific workers send email/SMS/push, record delivery state and expose status. At scale you may partition queues, rate-limit providers, retry transient errors and move failed work to a dead-letter queue.