System design cheat sheet
The 10 System design interview answers in 30 seconds each, plus quick facts. Printable, free.
One page per topic: print it or save it as PDF from your browser's print menu. Practise these questions
Rules of thumb
- State assumptions; convert to per-second rates and storage.
- Start with a modular monolith; split for teams or scaling needs.
- Read-heavy: cache and CDN in front of a simple store.
- Feeds: hybrid fan-out; celebrities merged at read time.
- Chat: per-conversation sequence, durable before ack.
- Payments: idempotency keys, webhooks, reconciliation, integer money.
- Big files go straight to object storage via pre-signed URLs.
Back-of-the-envelope estimates
Turn the product numbers into requests per second, storage and bandwidth with round figures, saying assumptions out loud. A month is about 2.6 million seconds, so 100 million new items a month is roughly 40 writes per second; multiply by the read/write ratio for reads, and by row size and retention for storage. The point is the order of magnitude, which decides whether one database is enough, whether you need caching, and where the bottleneck sits.
new_urls_per_month = 100_000_000
seconds_per_month = 30 * 24 * 3600
writes_per_s = new_urls_per_month / seconds_per_month
reads_per_s = writes_per_s * 100 # 100:1 read/write ratio
bytes_per_row = 500
five_year_tb = new_urls_per_month * 60 * bytes_per_row / 1e12
print(round(writes_per_s), round(reads_per_s), five_year_tb)Layering a backend service
A common split is four layers. The API layer handles HTTP: parsing, validation, auth, status codes. The service (domain) layer holds business rules and orchestrates work, with no HTTP or SQL in it. The data layer owns queries, transactions and caches behind repository interfaces. Infrastructure covers deployment, queues, config and observability plumbing. Dependencies point inward, so rules can be tested without a web server or database.
Monolith or microservices
Usually a modular monolith: one deployable with clear internal modules and boundaries. It's simpler to build, test, deploy and debug, and transactions just work. Microservices pay off when separate teams need to deploy independently, or parts need very different scaling or reliability, but they add network failures, distributed data, versioning and much more operational work. Extract a service when a module's boundary is proven and there's a concrete reason.
Design a URL shortener
Writes create a short code and store code → URL; reads look up the code and return a 301 or 302 redirect. Generate codes by base62-encoding a unique id (from a sequence or a range allocator per server), or hash plus collision check. Store in a key-value-friendly table with the code as primary key. Reads dominate, so cache hot codes in Redis and at the CDN. Add expiry, abuse checks on target URLs, and click counts sent asynchronously through a queue.
Design a news feed
Two approaches. Fan-out on write: when someone posts, push the post id into each follower's precomputed feed list (in Redis or a wide-column store), so reading is one fast lookup. Fan-out on read: build the feed at request time by merging recent posts from everyone you follow. Write fan-out breaks for accounts with millions of followers, so use a hybrid: fan out for normal accounts, and merge posts from celebrity accounts at read time. Paginate with a cursor.
Design a chat backend
Clients keep a WebSocket to a gateway tier; a presence or session store maps user to gateway. A send goes to the chat service, which assigns a per-conversation sequence number, stores the message durably, acks the sender, and routes it to recipients' gateways via a pub/sub bus. Offline users get a push notification and fetch missed messages by 'give me everything after sequence N'. Client-generated message ids make retries idempotent.
Design a payment flow
Never touch raw card data: the client tokenises the card with the provider, and your backend creates a payment with that token. Every create call carries an idempotency key, so a timeout and retry can't charge twice. Store a payment record with a state machine (created, authorised, captured, failed, refunded), update it from the provider's signed webhooks, and reconcile daily against the provider's reports. Money is integer minor units with a currency code, never floats.
Design a distributed rate limiter
Keep counters in a shared fast store, usually Redis, keyed by client and window, and update them atomically with a Lua script or INCR with expiry. A token bucket allows bursts up to its capacity; a sliding-window counter is smoother than fixed windows. Put the check at the gateway or in middleware, return 429 with Retry-After, and decide what happens if Redis is down: usually fail open for availability, with a local in-memory limit as a backstop.
Design search autocomplete
Precompute the top suggestions for every prefix from query logs (a trie or a prefix → top-k table), rebuilt by a batch or streaming job, and serve them from memory or Redis so each keystroke is a single lookup in a few milliseconds. Clients debounce keystrokes and cache recent prefixes; the CDN can cache popular short prefixes. Filter offensive terms and personalise lightly by merging a user's recent searches on the client or at the edge.
Design file uploads and storage
Keep file bytes out of your API servers. The API authorises the user and returns a pre-signed URL; the client uploads directly to object storage, in parts for large files so failed chunks can be retried. An event on upload completion triggers processing (virus scan, thumbnails) via a queue. Metadata (owner, name, size, checksum, status) lives in Postgres. Downloads go through short-lived signed URLs, often via a CDN.
Quick facts
- Seconds in a month
- 30 × 24 × 3,600 = 2,592,000, about 2.6 million.
- Base62 length 7
- 62⁷ = 3,521,614,606,208 possible codes.
- 0.1 + 0.2
- In binary floating point it equals 0.30000000000000004, so money uses integers or Decimal.
- Strangler fig
- Replace a legacy system piece by piece by routing one capability at a time to new code.