What system design is and why it matters
Open in the interactive app: practice, quiz, mock interview →In one minute
- System design is deciding how the parts of a software system are arranged, how they talk to each other, and where data lives, so that the whole meets its requirements under real load and real failures.
- The work starts from requirements, not technology: what users must be able to do, plus the numbers that matter (requests per second, data size, latency target, availability target, budget).
- Most designs are assembled from a small set of building blocks: load balancers, stateless services, caches, databases, queues, object storage and CDNs. The skill is knowing what each one costs and how each one fails.
- Every choice is a trade-off between latency, throughput, consistency, availability, cost and the effort to build and run it. A design is good when its trade-offs match the requirements and are written down.
How it works
Two halves: what and how much
A system has functional requirements, the things it does ("a user can upload a photo"), and non-functional requirements, the qualities it must have while doing them ("p99 upload under 2 seconds", "no acknowledged payment is ever lost"). Code mostly serves the first half. System design is mostly about the second.
The non-functional list is longer than speed and uptime. It also covers how easy the system is to operate and change, how secure it is, and what it costs to run. AWS's review checklist, for example, groups these into six pillars, including cost optimization and sustainability (AWS Well-Architected). Write down the constraints too: team size, deadline, compliance rules and the stack you already run. They rule out options as surely as a latency target does.
A photo app for a 40-person club runs on one server with a SQLite file. The same features for 50 million daily users need a CDN, object storage, sharded metadata, thumbnail workers and a plan for losing a data centre. Nothing in the feature list changed. The numbers did.
The method
A repeatable way to work through any design. Most curricula teach a version of it; the System Design Primer, for one, uses four steps: use cases and constraints, high-level design, core components, then scaling (System Design Primer).
- Clarify scope. List the three to five features that matter and say out loud what is out of scope.
- Put numbers on it. Estimate daily users, reads and writes per user, object sizes and retention. Turn those into peak QPS, storage and bandwidth. The Estimation Cheatsheet lesson collects the constants.
- Define the interface. Write the main API calls and the data shape. This exposes access patterns, which decide indexes and stores.
- Draw the simplest design that could work. Client, load balancer, stateless service, one database. Then ask where it breaks at the numbers from step 2.
- Fix the bottlenecks one at a time. Add a cache where reads dominate, a queue where work can be deferred, partitioning where one machine cannot hold the data, replicas where one machine cannot be allowed to fail.
- Walk the failure modes. What happens when a node dies, a zone goes away, a dependency gets slow, or a key gets hot? This often sends you back to step 4.
The building blocks, and where each is covered
- Spreading load. What are Load Balancers?, Load Balancing Algorithms, DNS Load Balancing, Anycast Routing.
- Making reads cheap. What is Caching?, Caching Strategies, Distributed Cache Architecture, Content Delivery Network.
- Storing data. Common Database Types and SQL vs NoSQL to choose; Indexing, Database Replication and Sharding to scale.
- Decoupling work. Message Queues, Pub/Sub, Message Brokers, Delivery Semantics.
- Surviving failure. Availability, Single Point of Failure, Circuit Breaker Pattern, Bulkhead Pattern.
- Agreeing on state. CAP Theorem, Consistency Models, Consensus Algorithms, Raft Algorithm.
The problem shapes underneath
Most design problems are a few recurring shapes: overload, hot spots, partial failure, contention (many writers, one record), fan-out (one event, many recipients), duplicates, ordering and finding things fast. A ticket sale and a viral post are both contention plus hot spots; a chat message and a push notification are both fan-out. Recognise the shape and you already know the shortlist of tools.
Down the stack
Layer 1The contract
The contract of a system is its service level objectives, stated so they can be tested. Google's SRE book separates three terms: the indicator (SLI) is the measured quantity, the objective (SLO) is the target for it, and the agreement (SLA) adds consequences such as refunds when the target is missed (SRE book, Ch 4). "Fast and reliable" is not a contract. "99% of GET /feed calls return in under 200 ms, measured at the load balancer over 28 days" is. So is "an acknowledged write survives the loss of any one availability zone."
Each clause constrains the design. Surviving a zone loss means synchronous cross-zone replication before acknowledging, a 1 to 2 ms round trip on every write. AWS places zones up to about 100 km apart, close enough for synchronous replication at single-digit millisecond latency (AWS fault isolation whitepaper). Writing the contract first lets you refuse complexity that no clause requires.
Layer 2The mechanism
Each building block delivers part of the contract through a concrete mechanism. A load balancer keeps a health-check state machine per backend. Redis keeps a hash table and, once you set maxmemory and an LRU policy, evicts by sampling a few keys (5 by default) and dropping the least recently used; on 64-bit builds there is no memory limit unless you set one (Redis docs). Postgres keeps a write-ahead log and B-trees, and replicates by shipping WAL records. Kafka keeps an append-only log per partition with consumer offsets. etcd runs Raft, committing an entry once a majority has it, so a 5-node cluster keeps working with 2 nodes down (Raft).
System design is choosing which mechanisms sit on each path. A feed read might go CDN, L7 proxy, service, Redis, and only on a miss a database partition chosen by consistent hashing. The write might go to the database leader, then through change data capture into Kafka, which fans out to caches and search.
Layer 3The machine
Everything above is bounded by physics. Rough numbers worth memorising (the classic table is Latency numbers every programmer should know; its SSD row of 150 us is from older drives):
- A main memory reference costs about 100 ns.
- An NVMe SSD random read is about 50 to 100 us; a durable
fsyncon a good SSD is roughly 0.1 to 1 ms, and on a spinning disk 5 to 10 ms. - A round trip inside one data centre is about 0.2 to 0.5 ms. Across zones in a region it is 1 to 2 ms. Across a continent it is 30 to 70 ms, and across an ocean 70 to 150 ms.
- A 10 Gbit/s NIC moves at most about 1.2 GB/s. A 25 Gbit/s NIC moves about 3 GB/s.
These numbers explain most designs. Caches exist because memory is about 1,000 times faster than an SSD read. Batching exists because fsync and round trips have fixed costs worth amortising. Regions exist because the speed of light makes a 150 ms round trip unavoidable across an ocean.
Why it's built this way
- One machine has hard limits on cores, memory, disk throughput and NIC bandwidth, and it fails as a unit. So large systems spread work and data across many machines.
- Machines talk over a network that is slow and can drop, delay or reorder messages, and a node cannot tell a crashed peer from a slow one (Kleppmann's lecture notes). So designs minimise round trips and plan for partial failure.
- Data that lives in several places can disagree. So every design picks a consistency model per piece of data, and pays for stronger ones in latency or availability.
- Hardware and people cost money. So the target is enough scale with headroom, and the simplest design that meets the contract wins.
Diagram
flowchart TD
R["Requirements: features + SLOs"] --> E["Estimates: QPS, storage, bandwidth"]
E --> A["API and data model"]
A --> H["Simplest high-level design"]
H --> B{"Breaks at target load?"}
B -->|yes| F["Fix bottleneck: cache, queue, shard, replicate"]
F --> H
B -->|no| X{"Survives node, zone, dependency failure?"}
X -->|no| F
X -->|yes| D["Document trade-offs and decisions"]Worked example
A team is building a link-preview service: given a URL, return the title, description and image of the page. Product expects 5 million daily active users, each triggering about 20 previews a day.
Numbers. 100 million previews a day is about 1,160 per second on average; plan for 4 times that at peak, about 4,600 per second. A sample shows 85% of requests hit a URL already fetched in the last day.
Simplest design. A stateless service fetches the URL, parses the HTML and returns JSON. A fetch takes 300 ms to 3 seconds plus about 20 ms of CPU to parse. At 4,600 per second that is 92 cores of parsing, thousands of open outbound connections, and a service that hammers popular sites.
Fix 1: cache. Put a shared cache keyed by normalised URL, with a 24-hour TTL, in front. 85% of requests now return in about 1 ms from Redis. Only 690 per second go out to fetch, which needs about 14 cores of parsing at peak.
Fix 2: decouple the slow work. On a miss the service returns a placeholder and enqueues the URL; workers fetch, parse and fill the cache. Duplicate misses collapse into one job keyed by the URL hash.
Fix 3: failure walk. If Redis is lost, every request misses: the queue absorbs the burst and previews degrade to placeholders. If one target site gets slow, a per-host concurrency limit (a bulkhead) stops it tying up every worker.
Fleet size. With 8 ms of CPU per cache-hit request at 16 cores per machine and a 60% utilisation target, the front tier needs ceil(4,600 × 8 / (16,000 × 0.6)), which is 4 machines, plus 2 spares so it survives a machine failure during a deploy. The practice task below builds exactly this calculator, checking CPU, memory and network to find which one binds.
In the wild
- AWS, 2017: S3 outgrows its own recovery path. On 28 February an engineer removing a few billing servers mistyped an input and removed capacity behind S3's index and placement subsystems in us-east-1. Both needed a full restart, which had not been done in the larger regions for many years while S3 grew a great deal, so recovery took from 9:37 to 13:54 PST. Even the status dashboard depended on S3. AWS made the tool remove capacity slowly, enforced minimum capacity, and sped up splitting the index into cells (AWS).
- Cloudflare, 2023: the failure walk that was never run. Facility PDX-04 lost utility power and generators, and the batteries, rated for about 10 minutes, began failing after 4. The control plane was meant to survive losing one facility, but some services quietly depended on Kafka and ClickHouse that ran only there, and a whole-facility test had never been done. Control plane and analytics were impaired for about 40 hours. Cloudflare now requires generally available products to run on the high-availability cluster and tests removing whole facilities (Cloudflare).
- Slack, 2021: the numbers changed overnight. On the first working day of the year, cold client caches and a traffic surge overloaded an AWS Transit Gateway. Autoscaling tried to add about 1,200 servers, which mostly failed on open-file and quota limits, and dashboards sat behind the same gateway. Slack was down or degraded from about 7:00 to 10:40 PST. Slack planned to request gateway upscaling ahead of holidays and to load-test provisioning (Slack).
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Single server with a local database | Cheapest, simplest to debug, no network failures between parts | One failure takes everything down; hard ceiling on scale | Prototypes, internal tools, low stakes |
| Stateless services behind a load balancer, one database | Easy horizontal scaling of compute, simple consistency | Database becomes the bottleneck and the single point of failure | Most products in their first years |
| Add caches and read replicas | Large read capacity, lower latency | Stale reads, invalidation bugs, cache stampedes | Read-heavy workloads with tolerable staleness |
| Partition data and add queues | Scales writes and storage, isolates slow work | Cross-partition queries and transactions get hard; more moving parts | Data or write rate exceeds one machine |
Pitfalls in production
- Designing for scale you will not have. Ten microservices and a sharded cluster for 50 requests per second burns months. Start simple and know the next step.
- No numbers. "We need Kafka" versus "Postgres is fine" cannot be settled without a write rate. Put QPS, payload size and growth on the page first.
- Ignoring the tail. Average latency looks fine while p99 is 2 seconds, and a page making 30 backend calls hits that tail on about a quarter of loads (
1 - 0.99^30). Set targets on percentiles; Google's SRE book makes the same case (SRE book, Ch 4), and Dean and Barroso show how fan-out turns rare slowness into the common case (The Tail at Scale). - Skipping the failure walk. A design never asked "what if this box dies?" hides a single point of failure, usually the database or config store.
- Undocumented trade-offs. Months later someone "fixes" eventual consistency into a latency regression. Record each decision and the requirement it serves.
Leadership lens
- Ask for requirements and numbers before boxes and arrows; a review that starts with a diagram ends in a debate about tools.
- Make every component justify itself by naming the requirement it serves; each one adds on-call load, upgrades, security patches and cost.
- Insist on a written failure walk for each critical path, with blast radius and detection time.
- Revisit the design when the numbers change by about 10 times. What was right at 1,000 QPS is rarely right at 100,000.
Check yourself
Two products have identical features but one has 1,000 times the users. Which parts of the design change and why?
Show answer
The non-functional side: capacity, partitioning, caching, redundancy and failure handling. Features drive the API; the numbers decide machines, where state lives and what breaks.
Why do caches appear in almost every large design?
Show answer
Memory is around a thousand times faster than an SSD read, and real workloads repeat reads. Serving repeats from memory cuts latency and protects the slower store.
A requirement says "acknowledged writes survive the loss of a zone." What does that force on the write path?
Show answer
The write must be durable in at least two zones before the client gets an acknowledgement, which means synchronous cross-zone replication and a 1 to 2 ms round trip added to every write.
Why can the fleet size be bound by network or memory rather than CPU?
Show answer
Each resource is a separate limit. Large responses saturate the NIC before the CPU, and long-lived connections hold memory per request for the whole latency, so whichever resource runs out first sets the count.
Sources and further study
Primary
- Hamilton, On Designing and Deploying Internet-Scale Services (LISA 2007): a checklist of design and operations practices, from "expect failures" to "keep things simple", written from running large services.
- Dean and Barroso, The Tail at Scale (CACM 2013): why percentiles, not averages, decide how a fan-out system feels.
Systems docs
- AWS Fault Isolation Boundaries: Availability Zones: what a zone is, how far apart zones sit and why that permits synchronous replication.
- Google SRE book, Ch 3: Embracing Risk: how a target is chosen and turned into an error budget.
Books
- Designing Data-Intensive Applications (2nd ed., 2026), Martin Kleppmann and Chris Riccomini, Ch 2 "Defining Nonfunctional Requirements": performance percentiles, reliability, scalability and maintainability, worked through a social-network timeline.
- System Design Interview, Volume 1, Alex Xu, Ch 3 "A Framework for System Design Interviews": a four-step method close to the one in this lesson.
- Fundamentals of Software Architecture (2nd ed., 2025), Mark Richards and Neal Ford, Ch 4 "Architectural Characteristics Defined": the non-functional "-ilities" and why every architecture is a least-worst trade-off.
Other curricula
- Azure Architecture Center: Design principles: frames design as a short list of principles (self-healing, redundancy, minimal coordination, scale out, partition around limits) that start from business requirements.
- roadmap.sh: System Design: frames system design as the blueprint of components, interfaces and data, then walks topic by topic through the building blocks.
Watch
- System Design Interview: Step By Step Guide (System Design Interview): requirements, estimates and a high-level design, step by step.
- Distributed Systems 1.1: Introduction (Martin Kleppmann): why systems are distributed and what that costs, from the Cambridge lecture series.
- System Design for Beginners Course (freeCodeCamp.org): a long tour of the common building blocks.
Free resources
- The System Design Primer (Donne Martin): a community-maintained index of building blocks and worked designs.
- Latency numbers every programmer should know: the classic table of memory, disk and network costs behind every estimate.
- Service Level Objectives (Google SRE Book): how to write the contract a design must meet.
- AWS Well-Architected Framework: a checklist of reliability, performance and cost questions to ask of any design.
- Distributed Systems lecture notes (Martin Kleppmann): the failure and consistency problems that shape large designs.