System Design
Back-of-envelope Math
How to size a system in 90 seconds. The four numbers that decide every architecture call.
Back-of-envelope math
Someone draws a box on the whiteboard, labels it "the database," and the room nods. Nobody has asked the one question that decides whether that box is a single Postgres instance or a sharded fleet with a cache in front of it: how many requests per second hit it? Back-of-envelope math is the discipline of answering that out loud, in your head, in the ninety seconds before anyone commits to a design.
The goal is not precision. It is to land in the right order of magnitude. A system that does 600 requests per second and a system that does 60,000 are different animals — different number of servers, different storage tier, different on-call pager volume. If you can tell those two apart on a napkin, you have already avoided the two most expensive mistakes in system design: building a distributed system for a load that fits on one box, and shipping a single box for a load that needs a hundred.
The four numbers
Almost every sizing question collapses to four numbers, computed in sequence. Each one feeds the next.
| # | Number | How to get it | What it decides |
|---|---|---|---|
| 1 | DAU | MAU × 0.2 (typical) | The scale of everything below |
| 2 | Avg RPS | DAU × actions/day ÷ 86,400 | App server count |
| 3 | Peak RPS | Avg × 5 (burst) | Your real capacity floor |
| 4 | Storage / year | volume/day × bytes × 365 | Storage tier and budget |
That is the whole toolkit. Four numbers, four lines of arithmetic. The trick that makes it fast in your head: 86,400 seconds in a day is roughly 100,000. Dividing daily volume by 100K is close enough for an order-of-magnitude answer and you can do it by moving a decimal point.
The point isn't to be precise — it's to be in the right order of magnitude. 60K RPS and 6K RPS are very different design problems. 60K and 70K are the same problem.
Worked example: a chat app
Walk it top to bottom. "Given" means a number the product hands you; everything else you derive.
| Step | Formula | Number |
|---|---|---|
| MAU | given | 100M |
| DAU | 100M × 0.2 | 20M |
| Avg messages / user / day | given | 50 |
| Total messages / day | 20M × 50 | 1B |
| Avg RPS | 1B ÷ 86,400 | ~11.6K |
| Peak RPS | 11.6K × 5 | ~58K |
| Bytes per message | given | 300 B |
| Bandwidth | 1B × 300B / day | 300 GB/day |
| Storage / year | 1B × 300B × 365 | ~110 TB |
Five derived numbers and the architecture has already taken shape. ~58K peak RPS means a fleet of app servers, not one — at maybe 5K RPS per node that's a dozen boxes with headroom. 110 TB/year means you are not keeping everything on one machine's local disk; you are sharding or tiering to object storage. And because chat is read-heavy, your cache layer has to absorb the vast majority of reads or the database melts on the first viral group thread.
Notice what the math did not tell you: whether to use Kafka or RabbitMQ, Postgres or DynamoDB. It told you the shape of the problem. That shape is what makes the next ten decisions obvious.
The numbers worth memorizing
You cannot size what you cannot compare against. These are the latency numbers — Jeff Dean's "numbers every engineer should know," still directionally correct decades later. They let you tell whether a design is physically possible before you write a line of code.
| Operation | Latency |
|---|---|
| L1 cache reference | 0.5 ns |
| L2 cache reference | 7 ns |
| Main memory access | 100 ns |
| SSD random read | 16 μs |
| Round-trip in same datacenter | 0.5 ms |
| Read 1 MB sequentially from memory | 250 μs |
| Read 1 MB sequentially from SSD | 1 ms |
| Disk seek | 10 ms |
| Read 1 MB sequentially from spinning disk | 30 ms |
| Round-trip cross-continent | 150 ms |
The shape that matters: each tier down is roughly a 100x jump. Memory is ~100x slower than L1, SSD is ~100x slower than memory, a cross-continent round-trip is ~100x slower than a same-DC one. If your request fans out to ten cross-continent calls in sequence, that's 1.5 seconds of pure network before any work happens. The math tells you to fan out in parallel, or to not fan across continents at all.
Memory is ~100,000x faster than a disk seek (100 ns vs 10 ms). This is the entire reason caches exist and the entire reason a cache miss on a hot path feels like the system fell off a cliff. When you reach for [CONCEPT]caching-patterns, this is the number you are exploiting.
Common multipliers
The conversions you apply without thinking once you've done a few of these:
| Factor | Multiplier |
|---|---|
| MAU → DAU | × 0.2 |
| Average → Peak | × 5 |
| Read : Write (social / chat) | 100 : 1 |
| Read : Write (e-commerce) | 20 : 1 |
| Read : Write (banking) | 10 : 1 |
| Working set : total data | ~0.01 (1%) |
| Headroom for growth | × 2 |
The read
ratios are the ones people skip and regret. A 100 read-heavy workload and a 10 one demand completely different architectures — the first lives or dies on its cache hit rate, the second cares about write throughput and durability. Guess the ratio wrong and you optimize the wrong half of the system.A war story: the peak nobody multiplied by five
A team launched a flash-sale feature for an e-commerce site. They did the sizing honestly — better than most. They pulled the numbers: 2M DAU, each browsing-user hitting the product API maybe 30 times a day. That's 60M requests a day, which divided by 86,400 is about 700 average RPS. They provisioned for 700, doubled it for headroom to 1,400, and called it generous.
The sale opened at noon. Within ninety seconds the product API was returning 503s, the cart service was timing out, and the on-call channel was a wall of alerts. The autoscaler was scaling, but cold-starting new nodes took two minutes and the stampede had already arrived.
The mistake was averaging across the wrong window. A flash sale does not spread its load over 86,400 seconds. The entire daily traffic arrived in the first ten minutes. Real peak RPS wasn't 700 or 1,400 — it was closer to 20,000, a 14x spike over the average they sized for. The ×5 peak multiplier they skipped would have gotten them to 3,500; even that was low for a flash sale, where the right multiplier is more like ×20. The sale lost an estimated several hundred thousand in revenue in the first hour, plus the customers who saw an error and never came back.
The fix was not more math — it was the right math. Sizing on the average hides the spike. The moment you hear "launch," "sale," "drop," or "going viral," the average is a lie and you size for the peak window, not the day.
The same trap, quieter: a nightly batch job that processes the whole day's events in one 20-minute window. The system's average load looks fine on the dashboard, so nobody provisions for the batch. Then the batch and a traffic spike land in the same window and the database tips over at 3am. Always ask: is this load spread out, or does it arrive all at once?
When to walk away from sizing
The four numbers tell you the shape of the problem, not the technology choice. They will not tell you Kafka over RabbitMQ, or Postgres over Dynamo — those decisions live in [CONCEPT]thinking-in-systems and the chapters that follow.
Sizing also stops being interesting at both extremes. Once your whole workload comfortably fits in a single box — a few hundred RPS, a few hundred gigabytes — stop sizing and ship the simple thing; you are inventing problems you don't have. And once you've committed to an architecture that scales horizontally without bound, the absolute number matters less than the per-node number and the headroom multiplier. The back-of-envelope is a tool for the messy middle, where the difference between one box and a fleet is exactly the question on the table.
[CONCEPT]thinking-in-systems is the prerequisite — its four questions tell you what to count. This lesson tells you how to count it, fast enough to do it on a whiteboard while everyone watches.