System Design Estimation for Beginners: Users to Servers
System design estimation step by step: turn daily users into peak requests per second, storage and servers, then link latency to load with Little's law.
By Matthews WongReviewed on
Key takeaway
System design estimation is arithmetic that turns written assumptions into the numbers a design must meet: daily users become requests per day, average and peak requests per second, storage, bandwidth and a server count. Little's law, L = λW, then links throughput to latency: it tells you how many requests are in flight at once, and how fast that grows when responses slow down.
You will be able to
- Turn daily users into average and peak requests per second, with every assumption written beside its number.
- Estimate storage, bandwidth and a server count, and say which one forces the design.
- Use Little's law to predict how slower responses change the concurrency the same traffic needs.
Step through the diagram
Press Play, or step with the arrows. Every step is written out below the diagram.
From daily users to servers: an estimate, step by step
Daily users times requests gives requests per day; dividing by 86,400 gives the average rate, a peak factor gives the design rate, one server's capacity gives the server count, bytes per user give storage, and Little's law gives the requests in flight.
Keys: ← → step · Home / End · K play or pause
An estimate is a chain. Each box will hold one number and each wire one written assumption, so changing an assumption changes every box after it.
Start from assumptions you write down: 1,000,000 daily users making 30 requests each. Multiply, and the system serves 30,000,000 requests a day.
A day has 86,400 seconds, so 30,000,000 a day averages about 350 requests a second. Round 86,400 to 100,000 when you work in your head.
Predict the next step Which rate should the servers be sized for? (The daily average, 350 a second · The busiest hour's rate)Traffic bunches into busy hours. Multiply by a peak factor from your own metrics, here an assumed 3, and design for about 1,040 requests a second.
Divide the peak by what one server handles safely, assumed 250 a second: 4.2, so 5 servers. Add a spare so one failure still leaves enough.
Bytes follow the same way. About 2 KB per user a day is 2 GB daily, 730 GB a year, and about 2.2 TB once you keep 3 copies.
Predict the next step Latency rises from 0.1 s to 0.5 s, same traffic. Requests in flight? (Stay about the same · Grow about five times · Fall to a fifth)Little's law: requests in flight equal arrival rate times time in the system. 1,040 a second × 0.1 s ≈ 104. At 0.5 s, the same traffic holds 520.
L = λ × W
What does system design decide before any boxes are drawn?
System design is deciding how a system's parts, data and interfaces fit together so it meets requirements you can state. The unit of work is a decision, and every decision costs something elsewhere: a cache makes reads fast and data stale, a queue makes responses quick and results arrive later.
Read more (2)Show less
So the work starts with requirements and numbers, not components. Write down what the system must do and how fast, how available and how correct it must be. Then estimate the load. Scaling is not a separate step; it falls out of that estimate, and when the estimate fits one server, the right number of new components is zero.
Architecture frameworks list the qualities a design has to answer for. AWS's Well-Architected Framework names six pillars: operational excellence, security, reliability, performance efficiency, cost optimization and sustainability. Buying more of one, such as reliability through a spare server, usually spends another, such as cost, which is why the trade-off belongs on paper next to the numbers.
How do you turn daily users into requests per second?
Multiply daily users by the requests each one makes, then divide by the 86,400 seconds in a day. One million users making 30 requests each is 30,000,000 a day, or about 350 a second. For mental arithmetic, round 86,400 to 100,000: the answer comes out about 14 percent low, which is fine at this precision.
Read more (3)Show less
That average is not what you size for, because traffic bunches into busy hours. Multiply by a peak factor taken from your own metrics; with an assumed 3, the design must carry about 1,040 requests a second. Split reads from writes as you go: 150 reads for every write points at caches and replicas, while a ratio near 1 points at the write path.
Then divide the peak by what one server can handle. That per-server figure is the weakest number in the chain, because a handler that runs one indexed query and one that resizes an image differ by orders of magnitude. Mark it as an assumption, replace it with a load-test result as soon as you can, and add a spare so one failure does not leave you short.
Guesses multiply. Three inputs that are each off by 2 times leave the result off by 8 times, so treat the output as an order of magnitude and keep every assumption next to its number, where anyone can challenge the wrong one.
How do storage and bandwidth change the design?
Bytes follow the same chain. Storage is records per day, times bytes per record, times days of retention, times the number of copies you keep. At 2 KB per user per day, a million users write 2 GB a day and 730 GB a year, and three copies make that about 2.2 TB. Forget the copies and you understate storage by exactly the copy count.
Read more (1)Show less
Bandwidth is the peak rate times the bytes per response. About 1,040 responses a second of 100 KB each is 104 megabytes a second. Links are sold in bits, so multiply by 8: about 830 megabits a second, close to a 1 Gbit/s port on its own. When egress exceeds the port, the answer is a CDN or object storage with its own edge, not a bigger application server, which is where the next module goes.
What is the difference between latency and throughput, and how does Little's law link them?
Latency is how long one request takes; throughput is how many requests finish per second. One is a duration, the other a rate. Adding servers raises throughput but does nothing for one slow request, and removing work from a request lowers its latency without adding capacity. Report latency as percentiles: p99 is the value 99 percent of requests beat, so a slow tail cannot hide behind a healthy average.
Read more (2)Show less
Little's law ties them together: the average number of requests in a system equals the arrival rate times the average time each spends there, L = λW, whatever the arrival pattern. At 1,040 requests a second and 0.1 seconds each, about 104 are in flight. If a slow query pushes latency to 0.5 seconds, the same traffic holds about 520 at once, five times the concurrency. That is how connection pools and worker limits run dry with no rise in traffic.
Utilisation adds the last warning. In the simplest queue model, one worker with random arrivals, the time in the system is 1 / (μ − λ), where μ is the service rate and λ the arrival rate. As λ approaches μ the wait grows without bound: at 90 percent utilisation a request spends 10 times its service time in the system, at 95 percent 20 times. Plan sustained load well below that knee. Google's Tail at Scale paper adds that brief latency spikes, unimportant in a moderate system, can come to dominate a large one.
The real config behind this lesson
Excerpts from the repository that builds and deploys this website, quoted as they are.
/** Requests per minute per client IP, enforced by each route. */
export const API_RATE_LIMITS = {
chat: 20,
followups: 30,
contact: 5,
} as const satisfies Record<keyof typeof API_PATHS, number>;This site writes its throughput limits down as numbers with their unit: requests per minute, per client IP. Converting them is the estimate run backwards: 20 chat requests a minute is one every 3 seconds per visitor, and multiplying by the visitors you expect bounds what the chat route can receive. The comment carries the unit, and a limit without a unit cannot be compared with anything.
mem_limit: 512m
cpus: "1.0"The servers box of this site's own chain: the whole web tier is one container capped at one CPU and 512 MB of memory. The repo holds no traffic estimate, so these caps are the capacity by decision rather than by arithmetic. An estimate is what would say when they stop being enough, and Little's law gives the early sign: if latency doubles at the same traffic, twice as many requests are inside that one CPU at once.
Mistakes that cost real time
Sizing for the daily average
Servers sized for 350 requests a second fall over at the 1,040-a-second peak, the one moment that matters. Size for the peak, with a factor taken from your busiest hour, and keep a spare for a dead node.
Mixing bits and bytes
Sizes are in bytes and links in bits. Forget the factor of 8 and 830 Mbit/s of egress looks like 104, which seems to fit a port it actually nearly saturates. Write the unit beside every number.
Running close to full utilisation
Waiting time grows without bound as utilisation nears 100 percent. In the simple queue model, a worker that is 95 percent busy keeps each request about 20 times its service time. Headroom is not waste; it is what keeps the tail latency bounded.
Treating the estimate as a spec
An estimate decides the class of design: one box or a fleet, a database or object storage. Freezing its guesses into a contract keeps wrong numbers alive. Once real traffic exists, replace each flagged assumption with a measurement, starting with the peak factor and the per-server rate.
| Latency | Throughput | |
|---|---|---|
| Question it answers | How long does one request take? | How many requests finish each second? |
| Unit | Milliseconds per request | Requests per second |
| Report it as | Percentiles: p50, p95, p99 | Sustained rate and the peak survived |
| You improve it by | Doing less work per request, fewer hops, caching | More copies, pooling, batching |
Do it on your own machine
You need Node 18 or newer, nothing else. Write the estimate as code so every assumption is visible, then measure Little's law on a server you run yourself.
Save the estimate as estimate.mjs and run node estimate.mjs. It prints the diagram's numbers: 347 average, 1,042 peak, 6 servers, 2.19 TB a year and 833 Mbit/s of egress.
// estimate.mjs: every input is an assumption; change one and run it again const a = { dailyUsers: 1_000_000, requestsPerUser: 30, peakFactor: 3, perServer: 250, // requests per second one server handles safely bytesPerUserPerDay: 2_000, // about 2 KB saved per user per day copies: 3, responseBytes: 100_000, // about 100 KB per response }; const perDay = a.dailyUsers * a.requestsPerUser; const average = perDay / 86_400; const peak = average * a.peakFactor; const servers = Math.ceil(peak / a.perServer) + 1; // plus one spare const storageTB = (a.dailyUsers * a.bytesPerUserPerDay * 365 * a.copies) / 1e12; const egressMbit = (peak * a.responseBytes * 8) / 1e6; console.table({ perDay, averagePerSecond: Math.round(average), peakPerSecond: Math.round(peak), servers, storageTBPerYear: Number(storageTB.toFixed(2)), egressMbitPerSecond: Math.round(egressMbit), });Change one assumption, peakFactor from 3 to 5, and run it again. The peak, servers and egress move; storage does not, because it never depended on the peak.
node estimate.mjsSave a server whose every response takes a fixed delay, and start it with a 100 ms delay.
// server.mjs: every response takes DELAY_MS milliseconds import http from "node:http"; const delay = Number(process.env.DELAY_MS ?? 100); http .createServer((req, res) => setTimeout(() => res.end("ok\n"), delay)) .listen(8080); // then, in a terminal: // DELAY_MS=100 node server.mjsIn a second terminal, send 50 requests a second for 10 seconds and average the requests in flight. Expect about 5, which is 50 × 0.1. Restart the server with DELAY_MS=500 and run it again: close to 25, five times as many for the same traffic.
// load.mjs: RATE requests per second for 10 seconds; average requests in flight const rate = Number(process.env.RATE ?? 50); let inFlight = 0; const samples = []; const sender = setInterval(() => { inFlight += 1; fetch("http://localhost:8080/") .then((res) => res.text()) .finally(() => (inFlight -= 1)); }, 1000 / rate); const sampler = setInterval(() => samples.push(inFlight), 50); setTimeout(() => { clearInterval(sender); clearInterval(sampler); const average = samples.reduce((a, b) => a + b, 0) / samples.length; console.log(`${rate} req/s -> ${average.toFixed(1)} in flight on average`); }, 10_000); // then: RATE=50 node load.mjs
Three questions before you move on
Pick an answer to see why it is right or wrong. Nothing is saved or graded, so guess freely.
Answer in your head, then check
1.Why is the per-server rate the weakest number in an estimate?Show the answer
Answer:Because handlers differ by orders of magnitude: one indexed query against an image resize. Mark it as an assumption and replace it with a load-test result.
2.Latency doubled but traffic did not change. Why might the app run out of database connections?Show the answer
Answer:Little's law: requests in flight equal arrival rate times latency, so doubling latency doubles the requests holding a connection at the same moment.
3.Why report p99 latency instead of the average?Show the answer
Answer:The average hides the slow tail. One request in a hundred taking seconds barely moves the mean, while p99 shows it directly.
Every post in this module, in order
The lesson above is the map. Each post goes deep on one part of it, with the full worked examples.
- System Design for Developers: What It Is and How to Learn ItSystem design is deciding how components, data and failures fit together. A plain-language guide with a worked sizing example and a learning path.
- Latency vs Throughput: Differences, Trade-offs and TuningLatency vs throughput explained: what each measures, how Little's law links them, why batching and queues trade one for another, and how to optimise both.
- Back-of-the-Envelope Capacity Estimation: QPS to ServersGo from daily active users to QPS, storage, bandwidth, cache size and server count, with a worked photo-sharing example and a TypeScript estimator.
Sources
Primary sources checked on the research date. Follow them for the current version.