InterviewPrepKit

Home / Learn / System Design

02 — Back-Of-The-Envelope Estimation

“Roughly how much storage does that need?”

This chapter teaches you to turn a one-line product description into a number — requests per second, terabytes, dollars, machines — spoken out loud, in under three minutes, with no notes and no calculator.

Behind the number sits the thing that actually gets graded. Every such estimate is a chain of assumptions multiplied together, one link in that chain almost always dominates the answer, and naming that link is the real deliverable.

When you finish you should be able to produce an estimate, say out loud which assumption it is most sensitive to, and check the result for plausibility even when there is nothing to check it against.

The input is a small set of assumptions you state: how many people use the thing, how often each of them does something, how many bytes one of those somethings is, and how long you keep it.

The output is one number plus a sentence naming the binding constraint — the resource that runs out first and therefore decides the design.

“1.5 PB per year” on its own is not an output. “1.5 PB per year, so a single machine is out by three orders of magnitude and the design question is really sharding — splitting the data across many machines” is. An estimate that produces a figure and no constraint has done nothing.

This is the only chapter in the track you will use out loud, under time pressure, with no notes. Treat it as a drill, not a read.

This chapter assumes no prior reading. Every constant it spends is written down in Latency numbers and what each one forbids and collected in Numbers worth memorizing cold, and anything borrowed from elsewhere is restated here before it is used. Chapter 01 is the architecture these numbers price, if you want to see where they end up.

The words and units used on every line

These terms and units appear on nearly every line below and are not defined again. Read them once now.

People and traffic.

Units of size. Byte units go up in thousands throughout this chapter — never 1,024.

UnitBytes
kilobyte (KB)1e3
megabyte (MB)1e6
gigabyte (GB)1e9
terabyte (TB)1e12
petabyte (PB)1e15
exabyte (EB)1e18

Units of time. The same idea, going down. us is the plain-ASCII spelling of the microsecond symbol.

UnitSeconds
millisecond (ms)1e-3
microsecond (us)1e-6
nanosecond (ns)1e-9

Bits are not bytes. Network links and video streams are rated in bits per second: Mbps is megabits per second, Gbps is gigabits, Tbps is terabits. One byte is 8 bits, so divide by 8 to convert a bit rate into a byte rate and multiply by 8 to go back. Forgetting that factor of 8 is the most common units slip in this chapter, and Estimation 4 is built on getting it right.

Notation. 1e5 is Python’s spelling of 1 x 10^5 = 100,000. This chapter uses it everywhere because it is faster to say and much harder to miscount than a row of zeros. 100 k means 100,000 and 500 k means 500,000.


1. The three numbers you actually need

Almost every estimate you will be asked for is one of three templates, or a product of two of them — and the habit that goes with them is announcing which one you are computing before you compute it.

TemplateWhat it decides
QPSDAU x actions/day / 1e5, then a stated peak multiplierFleet size, whether you need a queue
Storageitems/day x bytes/item x retention x replicationOne box vs sharding vs tiering
BandwidthQPS x bytes/responseCDN, egress cost, saturating a machine’s network card

Three things in that table need unpacking before you can use it.

The 1e5 in the first row is the 86,400 seconds in a day rounded to one significant figure. Dividing by it turns a per-day count into a per-second rate by moving the decimal point five places, which you can do in your head. Rounding discipline is where that approximation is justified and its error bounded.

Shard and tier, in the third column, are the two answers the storage template usually points at. To shard is to split one dataset across several machines that each hold a disjoint slice. To tier is to move rarely read data onto cheaper, slower storage.

Memory is a fourth template, and it is almost always working set x bytes/entry — the working set being the portion of the data actually being read at any moment. It is usually asked as “does the cache fit?”, which And does it fit.

Say which one you are computing before you compute it. “Let me size the write path” is a sentence that buys you thirty seconds of thinking time and signals you know these are different questions.

Every estimate is a chain of assumptions

One idea underlies everything else in the chapter: an estimate is not a measurement with error bars, it is a product of guesses, and your job is to know which guess is carrying the answer.

Look at the shape of all three templates above and one thing is true of every one of them — they are products. DAU x actions/day. items/day x bytes/item x retention x replication. QPS x bytes/response. Nothing is added; everything is multiplied.

That single structural fact gives you the whole sensitivity analysis for free, because in a product, a factor that is 2x too high makes the answer exactly 2x too high, no matter how large or small that factor is in absolute terms. A million-fold number you know precisely damages the answer less than a small number you guessed.

So rank your assumptions not by size but by spread — the ratio between the highest and the lowest value you would not be embarrassed to defend. The widest spread is the assumption that dominates, and it is the one to say out loud and to offer to redo.

The code below does that ranking mechanically. Its input is one (low, high) pair per assumption; its output is the assumption names sorted by high / low. Read the printed column rather than the function body — what matters is which name comes out on top.

def spread(factors):
    """Rank assumptions by how much each one can move the answer.

    `factors` maps a name to (plausible_low, plausible_high). The estimate is
    a product, so each factor scales the answer by its own range and the
    ratio high/low IS that factor's contribution to the total uncertainty.
    Absolute magnitude is irrelevant: a factor of 1e9 known exactly
    contributes 1.0x, and a factor of 0.2 that might be 1.0 contributes 5x.
    """
    return sorted(((name, hi / lo) for name, (lo, hi) in factors.items()),
                  key=lambda pair: -pair[1])


# The photo-storage estimate of section 4, with an honest range on each input.
photo = {
    "users":            (2.5e8, 1.0e9),   # "500 M" is a placeholder, so 2x either way
    "uploads/user/day": (0.05,  1.0),     # 0.2 assumed, and this is the softest number
    "bytes/upload":     (1e6,   4e6),     # 2 MB after transcode, depends on the codec
    "retention days":   (1825,  1825),    # 5 years, stated by the question
    "replicas":         (3,     3),       # a design choice, not a guess at all
}
for name, ratio in spread(photo):
    print(f"{name:18} {ratio:5.1f}x")

# uploads/user/day    20.0x   <- the answer is this assumption
# users                4.0x
# bytes/upload         4.0x
# retention days       1.0x
# replicas             1.0x

assert spread(photo)[0][0] == "uploads/user/day"

The uploads-per-user figure is twenty times softer than anything else in the chain, so it is the number the estimate is really about.

Now multiply the ratios together. The end-to-end estimate spans 20 x 4 x 4 = 320x, which sounds like it invalidates the exercise.

It does not, and understanding why is the point of the whole chapter. The conclusion that estimate reaches — that this is an object-storage problem with a small metadata database beside it, and that keeping three full copies of cold data is indefensible — is true at every point across that 320x range.

Check the endpoints rather than asserting it. Each corner multiplies the same five factors, once at every low value and once at every high value:

low corner    2.5e8 users x 0.05 uploads x 1e6 B x 1,825 days x 3 copies
              =  6.8e16 B  =   68 PB
high corner   1.0e9 users x 1.0  uploads x 4e6 B x 1,825 days x 3 copies
              =  2.2e19 B  =   22 EB

Object storage with tiering is the answer at both ends. An estimate is robust when its conclusion survives its own uncertainty, not when its number is accurate.

Two habits follow, and both are cheap:

QPS, worked end to end

Every problem in the track opens with the QPS template, so have this walk — from product description to fleet-sizing number — memorized.

The assumptions. These five lines are the entire input, and everything after them is arithmetic:

The dominant assumption is openings per day. The DAU figure and the openings figure are both placeholders invented to make the arithmetic concrete, but a user count is the kind of thing a company knows, where “5 opens a day” could as easily be 3 or 10. Flag that one out loud.

The arithmetic. The left column is per-day totals; the right column is the per-second rate each becomes after dividing by the 1e5 seconds in a day.

reads    2e8 users x 5 opens    =  1e9 requests/day  ->  / 1e5 s  =  10,000 /s   average
writes   2e8 users x 0.1 posts  =  2e7 requests/day  ->  / 1e5 s  =     200 /s   average

peak multiplier 2x (consumer traffic, one diurnal hump)
         reads    10,000 /s x 2  =  20,000 /s
         writes      200 /s x 2  =     400 /s

read:write   =  1e9 / 2e7  =  50:1

The findings. Three fall out, and the ratio is the one that matters:

Then convert back before you buy anything:

1e9 requests / 86,400 s  =  11,574 /s  average
11,574 /s x 2            =  23,148 /s  peak

The 1e5 version was for deciding; the 86,400 version is for provisioning.

The peak multiplier is a stated assumption, not a derived constant, and it deserves its own sentence out loud. It says how much busier the busiest second is than the average second.

Use 2x for smooth consumer traffic with one diurnal hump — one broad rise and fall across the waking day. Use 3x for event-driven or bursty workloads, where a notification or a broadcast concentrates arrivals into minutes. Say which you picked and why.

Some systems legitimately skip the multiplier entirely, because a tier behind a queue absorbs spikes by design rather than by capacity. Ch 14 drops it for its video-transcoding tier for exactly that reason and still provisions at roughly 1.5x the mean. What is not acceptable is applying the multiplier silently or omitting it silently.


2. Rounding discipline

You cannot do 500,000,000 x 287 on a whiteboard, and nobody wants you to. Some approximations are free, one is the highest-leverage of all, and in exactly one place rounding is forbidden.

A significant figure is a digit that carries information. Rounding to one of them means keeping the leading digit and replacing the rest with zeros, so 287 becomes 300 and 86,400 becomes 100,000.

The rule is round every input to one significant figure, and these are the substitutions to know by reflex:

seconds/day       86,400   ->  1e5      (you are 16% high; say so once, move on)
seconds/month     2.6e6    ->  2.5e6    (two figures on purpose: the exact 2.592e6
                                         rounds to 3e6 at one figure, and that is
                                         16% high, where 2.5e6 is only 3.5% low)
1 million/day                 10/s      (exact 11.6)
1 billion/day                 10,000/s  (exact 11,574)
1 KB = 1e3 B, 1 MB = 1e6 B, 1 GB = 1e9 B   (never 1,024 in an estimate)

1e5 seconds/day is the single highest-leverage approximation in the interview. It turns every per-day figure into a per-second figure by moving the decimal point five places.

Its error is easy to state, so state it: 1e5 / 86,400 = 1.16, meaning the divisor you used is 16% larger than the real one. That is far below the error in your input assumptions, and saying that you know it is there is worth more than the accuracy you gave up.

When to convert back

The 1e5 shortcut stops being acceptable at a precise moment, and using it one step too long is how a fleet ends up undersized.

1e5 is a drill convention, not a reporting convention, and the difference is where this trips people. Divide by 1e5 while you are thinking out loud. Divide by 86,400 the moment a number leaves your mouth as a result — a fleet size, a monthly bill, a capacity headroom.

1 B/day  ->  via 1e5:   10,000/s     the number you reason with
         ->  exact:     11,574/s     the number you size a fleet with

Be careful about which 16% is which. The 16% is the error on the divisor; the shortfall on the quotient is its reciprocal, 10,000 / 11,574 = 0.864. So 1e5 understates load and a fleet sized on it is 13.6% short, not 16%. That is fine for “do I need a queue?” and not fine for “how many boxes do I buy?”

This is why chapters in this track legitimately differ. The ones drilling a decision divide by 1e5; the ones costing a fleet divide by 86,400. Say which one you are doing. An undeclared convention is the actual defect, not the choice.

The one place not to round

There is exactly one place not to round, and it is powers of two, when the answer is a bit width — how many binary digits you allocate to a field in an identifier.

Those are not estimates at all. They are exact capacities, and being 16% off changes whether a design works. 2^32 is 4.3 billion, so a 32-bit counter overflows after 4.3 billion values. 2^41 milliseconds is 69.7 years, so 41 bits of millisecond timestamp last a human lifetime.

Those specific values decide designs, as Data model the 64 bit layout derived shows when it packs a unique identifier into 64 bits.

2^10  ~ 1e3     2^20  ~ 1e6     2^30  ~ 1e9     2^32  = 4.3e9
2^40  ~ 1e12    2^41 ms = 69.7 years            2^64  = 1.8e19

3. Latency numbers, and what each one forbids

The table below is the standing set of hardware constants the whole chapter spends. Every estimate below draws its physical numbers either from this table or from the collected list in Numbers worth memorizing cold, and from nowhere else. When an estimate says “at 1 GB/s sequential”, “at 500 us per hop”, “at 20 TB per drive” or “at $0.02/GB of egress”, the constant came from one of those two places and you can point at the row.

The split between the two is deliberate. This table is the timings. §6 repeats them and adds the capacities, prices and stated defaults that are not latencies at all.

§6 is not a strict subset, though, and the difference is worth knowing before you drill it. Two of its latency rows appear only there — the ~0.5 ms cache read and the ~10 ms database read — and both are stated assumptions about a loaded system rather than hardware constants, which is exactly why they are kept out of this table.

One abbreviation appears throughout: RTT is round-trip time, one message sent plus its answer coming back.

Read the vintage note before you use the table. It descends from Jeff Dean’s 2009 numbers (the Stanford talk they come from, and the widely circulated list that copied them). The ones set by physics — the speed of memory, the speed of light in fibre — have not moved in the years since. Two rows are marked with a ⚠ and they fail for different reasons: the SSD row (a solid-state drive, flash storage with no moving parts) gives a per-operation latency that gets misread as a limit on the whole device, and the disk-seek row is correct for the device it names and is routinely quoted for a device that has no seek at all.

The table itself is famous. What matters is the third column — the design rule each row implies — which is the part candidates skip. Read every row as an assumption about hardware, kept deliberately separate from the assumptions about workload that each estimate states for itself. An interviewer will challenge a workload assumption and accept a hardware constant, so you want to know at every moment which of the two you just used.

OperationTimeWhat it forbids
L1 cache reference1 ns
Branch mispredict3 ns
Main memory reference100 ns1e7/s per core, so in-memory is ~free at any web scale
Compress 1 KB (snappy)2 usCompress before crossing a network, always
Send 1 KB over 1 Gbps10 us
SSD random read (4 KB, one at a time)100 usThe latency of one read. Do not turn it into a device ceiling — see below
Read 1 MB sequential from memory250 us
Round trip within a datacenter500 us~2,000 sequential hops/s. Chains of 10 internal calls cost 5 ms before any work
Read 1 MB sequential from SSD1 ms
Disk seek (spinning hard disk only)10 ms100/s per spindle. Applies to HDD; an SSD has no seek. This is why B-tree depth matters — see below
Read 1 MB sequential from disk20 ms
Round trip, US-East <-> Europe~75 msThe shorter transatlantic path, and the figure ch 01 uses for its EU users
Packet CA -> Netherlands -> CA150 msYou cannot serve Europe from California. This single number forces multi-region

Two rows carry vocabulary that the table cell had no room for.

“Spinning rust” is the engineers’ nickname for a hard disk with rotating platters, the device the 10 ms seek row describes.

A B-tree is the balanced, high-fanout index a relational database keeps on disk, and a lookup walks it from root to leaf — so its depth is the number of seeks one row costs, which is why 10 ms per seek shows up in query latency (sql/03).

The last two rows are different routes, not one route and its double. ~75 ms is a round trip on the short transatlantic path, US-East to Europe and back. 150 ms is a round trip from California, which crosses the continent before it crosses the ocean. The ratio is about 2x by coincidence of geography, and reading the second as “the first, there and back” is a mistake that then gets applied to every other pair of cities.

The two rows that will burn you

Two rows get misread more often than all the others combined, and both misreadings turn a workable design into an apparently impossible one.

Two terms first. IOPS means input/output operations per second, the count of separate reads or writes a storage device completes each second. Queue depth is how many requests the device is working on at the same time, so queue depth 1 means you wait for each read to finish before issuing the next.

100 us is a latency, and dividing 1 into it gives 10,000 IOPS — which is a queue-depth-1 number and roughly 50x below a real NVMe device. NVMe is the modern interface by which solid-state drives attach directly to the processor bus, and such a drive does 500 k–1 M random 4 KB IOPS because it services hundreds of requests concurrently; the per-request latency stays around 100 us while throughput scales with queue depth.

Use 100 us to answer “how long does one read take?” and never to answer “how many can this box do?” Treating it as a device ceiling is how a storage estimate concludes that a design is impossible when the real device is fifty times faster than the arithmetic assumed.

10 ms is a seek, and only a spinning hard disk — an HDD, or hard disk drive — actually seeks. A seek is the physical movement of a read head to a track on a rotating platter. A solid-state drive has no head and no platter, so quoting 10 ms for SSD-backed storage inflates every input/output argument by two orders of magnitude.

Two conclusions from the table are worth memorizing as sentences rather than as rows:

A third conclusion needs a table of its own.

Sequential versus random, and why the block size matters

Sequential means reading bytes that are next to each other on the device. Random means jumping to an unrelated location for each read. Sequential wins, but by how much depends entirely on a number people forget to quote: the block size, the number of bytes one random read fetches.

Sequential throughput comes straight off the table’s own rows. Random throughput is one block per operation, so it is block size / latency: an SSD’s 100 us gives 1 / 100e-6 = 10,000 IOPS, and a disk’s 10 ms seek gives 1 / 10e-3 = 100 IOPS.

SequentialRandom, 1 KB blocksRandom, 4 KB blocks
SSD1 MB in 1 ms = 1,000 MB/s (1 GB/s)10,000 IOPS x 1 KB = 10 MB/s10,000 IOPS x 4 KB = 40 MB/s
HDD1 MB in 20 ms = 50 MB/s100 IOPS x 1 KB = 0.1 MB/s100 IOPS x 4 KB = 0.4 MB/s
SSD advantage1,000 / 10 = 100x1,000 / 40 = 25x
HDD advantage50 / 0.1 = 500x50 / 0.4 = 125x

Quote the block size or the ratio is unfalsifiable. Moving from a 1 KB block to a 4 KB page carries four times the bytes per operation, which cuts the sequential advantage by exactly 4x on both devices.

Either way the direction of the inequality is what makes log-structured storage engines exist — engines that never update a page in place but append every write to the end of a file and merge the files later (Lsm trees vs b trees) — since they convert random writes into sequential ones on purpose.


4. Six worked estimations

Now the drill itself: six estimates, each of a different shape, each ending in a named constraint rather than a number. There is also a short seventh, “does the cache fit?”, worked as a coda to Estimation 5 because it is the same cache seen through the memory template.

Between them the four templates of The three numbers you actually need are all worked at least once:

EstimationTemplate it works
1 — Twitter-scale tweet storagestorage (pure byte arithmetic; it computes no rate at all)
2 — Photo service, 5-year storagestorage
3 — Chat: connection memorymemory
4 — Video streaming egressQPS and bandwidth, on the same workload
5 — A cache’s hit-rate economicsmemory, in the “does it fit?” coda
6 — ID generation rateQPS

Do them end to end, out loud, on a timer. Each should take under three minutes.

Every one of them follows the same three-part structure, and it is worth naming the structure before the first example so you can hear it repeating:

  1. The assumptions, stated out loud and written down — because an assumption in your head is the defect The estimation checklist is about.
  2. The arithmetic, rounded ruthlessly, with units carried on every line.
  3. The finding — which resource binds, and therefore what the design conversation is actually about.

Each one also names the assumption that dominates: the one whose plausible range is widest, which therefore owns the answer.

Estimation 1 — Twitter-scale tweet storage

How many bytes does a text-and-image social product accumulate? The lesson is that “how much storage” is almost never a question about the text.

Assumptions.

The dominant assumption is the image fraction. It could plausibly be 5% or 30%, a 6x range, where a published tweet count is usually good to well within 2x and the text size barely moves anything at all. Since the media term turns out to be 99.7% of the total, whatever happens to that fraction happens to the answer.

Arithmetic.

text     5e8 tweets x 300 B                 =  1.5e11 B  =    150 GB/day
media    5e8 tweets x 0.10 x 1e6 B          =  5.0e13 B  =     50 TB/day
                                              ---------
total                                          5.015e13 B =  50.2 TB/day

retention   5 years x 365 days              =  1,825 days
            50.2 TB/day x 1,825 x 3 copies  =  2.75e17 B  =  275 PB

Finding: the text is 0.3% of the bytes. This is an object-storage problem with a small metadata database bolted on, not a database problem. Anyone who spends the interview on the tweet table’s schema has mis-sized the system.

The media does not belong in the same store either. It goes to blob storage — a service that keeps whole files under a name and is cheap per byte — with a CDN (a content delivery network, a rented fleet of servers worldwide that caches those files near users) in front of it. Only the URL lives in the database.

Check. The line worth checking is 275 PB, and it needs no calculator:

50 TB/day x 365 days  =  18 PB/year
18 PB/year x 5 years  =  90 PB
90 PB x 3 copies      =  270 PB

Landing within 2% of the long form by a completely different route is what makes the number safe to say out loud.

Estimation 2 — Photo service, 5-year storage

The same storage question at exabyte scale. The lesson is that the useful part is the follow-up about how you store it, not the total.

Assumptions.

The dominant assumption is uploads per user per day. As the sensitivity analysis above showed, it dominates by 20x and everything else in the chain is comparatively firm.

Arithmetic.

per day     5e8 users x 0.2 uploads x 2e6 B  =  2.0e14 B   =  200 TB/day
per year    200 TB/day x 365 days            =  7.3e16 B   =   73 PB/year
5 years     73 PB/year x 5                   =  3.65e17 B  =  365 PB
3 replicas  365 PB x 3                       =  1.095e18 B =  1.1 EB

Finding: an exabyte is the answer, and the follow-up question is the real one. You do not keep 3 full replicas of cold photos.

Erasure coding is the alternative. Cut each object into fragments, compute a few extra parity fragments from them, and spread the set across machines so that any sufficiently large subset reconstructs the original. It survives the same number of failures as replication while storing roughly 1.5x the original bytes instead of 3x.

Do that subtraction carefully, because it is the single easiest slip to make here:

3 replicas    365 PB x 3    =  1,095 PB
1.5x erasure  365 PB x 1.5  =    547.5 PB
saved         1,095 - 547.5 =    547.5 PB   (not the 365 PB raw figure)

Tiering — moving data that is rarely read into a cheaper, slower storage class — saves more again, though which tier is a real decision rather than a reflex. Cold storage costs about a fifth as much per byte but charges a fee every time you read something back, so it wins only below 0.4 reads per PB per month (Garbage collection scrubbing and lifecycle derives that threshold and asks you to quote it exactly). The estimate’s job was to make tiering obviously necessary rather than a nice-to-have.

Check. Sanity-check the exabyte before you trust it, because nobody has an intuition for that unit. Turn it into a physical object instead:

drives   1.095e18 B / 2e13 B per drive  =  54,750 drives   (~20 TB/drive, §6)
racks    54,750 drives / 600 per rack   =      91 racks    (~600 drives/rack)

Carry the conversion constants, not just the words. A U, or rack unit, is the standard 1.75-inch height increment that rack-mounted equipment is built in, so a 42U cabinet is 42 slots tall and holds ten 4U enclosures at 60 bays each — 10 x 60 = 600, which is where that divisor comes from. A reader who does not know what a U is cannot run the next check, which is a check against the 600.

Ninety racks is one row of a data hall. That is a plausible size for a global photo service and an implausible size for a startup, which is exactly the kind of judgement the number was computed to support.

Run the next check on the check. “A thousand racks and several buildings” is the answer people reach for here, and it fails a bound you already believe: a thousand racks for 55,000 drives implies 55 drives in a rack, which is a tenth of what a storage rack holds. An order-of-magnitude error in a sanity check is worse than no sanity check, because it is the number you will say with confidence.

Estimation 3 — Chat: connection memory, and a trap

This estimate deliberately produces a wrong answer. The lesson is the most useful thing an estimate can do: prove that the resource you measured is not the one that sizes the tier.

Assumptions.

The dominant assumption is not the 10 KB. That is firm to about 2x, and it stops mattering the moment you learn memory is not the constraint. The dominant one is connections per box, which plausibly spans 100 k to 500 k, a 5x range that sets the fleet outright.

Arithmetic.

2e7 connections x 1e4 B  =  2e11 B  =  200 GB
200 GB / 64 GB per box   =  3.1     ->  ~3 boxes

That 64 GB is the bottom of the commodity range, not the 128 GB Numbers worth memorizing cold tells you to default to, so say so out loud. At the default it is 200 / 128 = 1.6, call it 2 boxes. Taking an end of a range silently is how two people size the same tier differently and never find out why.

Finding: three boxes is the wrong answer, and knowing why is the point. Memory is not the binding constraint on a tier that holds connections open. Four other things are, and each is a real ceiling:

Real deployments land at 100 k–500 k connections per box — and that is a stated default, not a measurement. It is an order-of-magnitude anchor of exactly the kind Numbers worth memorizing cold collects, it spans 5x, and it is the number that sets this fleet outright. Say it out loud and abandon it the instant a load test on your own stack gives you a real one.

20 M / 500 k per box  =   40 boxes
20 M / 100 k per box  =  200 boxes

The estimate tells you which resource is not the constraint. That is a legitimate and often more useful outcome than a fleet size. Say it out loud: “Memory says three boxes, which tells me memory isn’t what sizes this tier — it’s connection-handling and failure domain.”

Estimation 4 — Video streaming egress

This estimate prices the bytes leaving a video service. The lesson is that at video scale the bandwidth bill is not a line item in the business — it is the business.

Two words first. Bitrate is how many bits of video are consumed per second of playback. Egress is the industry word for bytes leaving your network, which is the direction cloud and CDN providers charge for.

Assumptions.

The dominant assumption is watch time. View counts are usually known, but “5 minutes” could plausibly be 2 or 15, and it multiplies the entire answer.

Arithmetic. Watch the / 8 on the third line: the bitrate is in bits and everything downstream is in bytes.

bytes/view   5 min x 60 s/min             =  300 s of playback
             300 s x 3e6 bits/s           =  9.0e8 bits
             9.0e8 bits / 8               =  1.125e8 B  =  110 MB  (rounded down)
per day      1e9 views x 1.1e8 B          =  1.1e17 B   =  110 PB/day
CDN egress   1.1e17 B / 1e9 B per GB      =  1.1e8 GB
             1.1e8 GB x $0.02/GB          =  $2.2 M/day =  $800 M/year

the same total as bandwidth, QPS x bytes/response:
             1e9 views/day / 1e5 s        =  10,000 views/s
             10,000 /s x 1.1e8 B          =  1.1e12 B/s
             1.1e12 B/s x 8 bits/B        =  8.8e12 bits/s  =  8.8 Tbps average

Those last three lines are the same estimate run through the bandwidth template instead of the storage one, and it is worth doing both because they answer different questions. The per-day route gives you the bill. The QPS x bytes/response route gives you the pipe, and 8.8 Tbps sustained is 880 completely saturated 10 Gbps network cards before any peak multiplier. That is the arithmetic proof that the origin cannot serve this traffic and the CDN is not an optimization.

Finding: egress is the business. At this scale a 10% reduction in bitrate from a better codec — the algorithm that compresses and decompresses video — is worth $80 M a year, which is why video companies employ codec teams.

It also reframes the design: the origin serves almost nothing, the CDN serves everything, and cache hit ratio at the CDN edge is the single most valuable metric in the system.

Check. Verify the $2.2 M a day before quoting it, because a number that large is where a units slip hides. 110 PB is 110 million GB, and 110 million times two cents is 2.2 million dollars. The check took one multiplication and it is the difference between a defensible figure and a memorized one.

Say which way you rounded while you are there. 1.1e8 is a rounded-down 1.125e8, so every figure in the block is 2.2% low, and the unrounded chain gives $2.25 M/day and $821 M/year. That gap is far inside the spread on watch time and not worth carrying — but “$800 M” offered as though it were exact is a different claim from “$800 M, and I rounded down to get there”.

Estimation 5 — A cache’s hit-rate economics

This estimate prices what a cache buys. The lesson is one asymmetry people get backwards: the load a cache removes and the factor by which it removes it behave completely differently as the hit rate climbs.

A cache is a small, fast store that holds recently requested answers. A hit is a request it can answer; a miss is one that falls through to the database. Write the hit rate as h, a fraction between 0 and 1.

Assumptions.

The dominant assumption is the hit rate itself, which is why the estimate is run at three values of it rather than one.

The 10 ms is a stated assumption, not the disk-seek row of Latency numbers and what each one forbids reused. A database read is not one seek. It is a buffer-pool miss — the buffer pool being the database’s own in-memory cache of disk pages, so a miss is a row that was not already in RAM — plus a network hop plus query execution on a primary that is also serving everyone else. 10 ms is a defensible round number for that on a loaded system.

On an SSD-backed primary with a warm buffer pool, meaning one already holding the pages this workload touches, it could be 1 ms. That 10x range makes it the widest assumption in this estimate after the hit rate. Quoting the table’s 10 ms here and calling it a hardware constant is exactly the misuse that row is marked with a ⚠ for.

Arithmetic. Mean latency is the hit path and the miss path weighted by how often each happens.

assume  100 k QPS read, DB read 10 ms, cache read 0.5 ms

hit rate 0.80:  mean latency = 0.8(0.5) + 0.2(10)  =  2.4 ms
                DB load      = 100k x 0.20         =  20 k QPS
hit rate 0.90:  mean latency = 0.9(0.5) + 0.1(10)  =  1.45 ms
                DB load      = 100k x 0.10         =  10 k QPS
hit rate 0.99:  mean latency = 0.99(0.5)+0.01(10)  =  0.595 ms  (say 0.6)
                DB load      = 100k x 0.01         =   1 k QPS

Both of those columns are straight lines in h. Latency is 10 - 9.5h and load is 1e5 - 1e5h, so “linear in the hit rate” and “linear in the miss rate” are the same sentence about the same line, and neither one is the finding.

An affine function is a straight line — anything of the form a + b h, a constant plus a multiple of the variable. Any affine function of h is an affine function of 1-h, because substituting h = 1 - (1-h) gives (a + b) - b(1-h), the same line with the constants relabelled. There is no asymmetry there to get backwards.

Finding: the real asymmetry is between the load and the factor. Load is QPS x (1-h), a line that falls to zero. The reduction factor is 1/(1-h), a hyperbola that diverges: at h = 0.80 the cache is doing 5x, at 0.99 it is doing 100x, at 0.999 it is doing 1,000x.

Conflating the two is the classic slip. As h -> 1 the factor goes to infinity while the thing it is a factor on goes to zero, which is why the last fraction of a percent is simultaneously the most valuable and the least material.

The clean way to say it is in nines:

h = 0.80  ->  miss 0.200  ->  20,000 QPS
h = 0.90  ->  miss 0.100  ->  10,000 QPS     2x better
h = 0.99  ->  miss 0.010  ->   1,000 QPS    10x better
h = 0.999 ->  miss 0.001  ->     100 QPS    10x better again

Each additional nine of hit rate divides database load by ten. That is why the last points are worth disproportionate effort — and why a cache stampede, in which a popular key expires and every request for it misses at the same instant (Step 4 cache and the ceiling that forces it), is so violent: dropping from 99% to 90% is one nine backwards and 10x the database load instantly.

And does it fit?

Everything above prices a hit rate you have not yet shown you can buy. The memory template of The three numbers you actually needworking set x bytes/entry — is what answers that. It is the fourth template and the one interviewers phrase as “does the cache fit?”, so work it in the same breath as the hit rate rather than assuming the answer.

Assumptions. Take the product of Estimation 1: 500 M items a day, 300 bytes of text each, and a hot window of one day, since almost all reads on a feed are of recent items.

Arithmetic.

assume  hot window 1 day = 5e8 entries, ~1 KB each in the cache
        (300 B of text plus the key, pointers and serialization overhead)

working set   5e8 entries x 1e3 B   =  5e11 B  =  500 GB
usable/box    128 GB x 0.70         =  89.6 GB      (§6 default, 70% full
                                                     to leave eviction headroom)
boxes         500 / 89.6            =  5.6     ->  6 boxes

Finding: six boxes, so it fits — and that is the finding, because it means the 99% hit rate above is a real option rather than an aspiration.

The dominant assumption is the hot window, not the entry size: one day against one week is a 7x swing straight into the box count, where the 1 KB is firm to about 2x.

Note also the roughly 3x between the 300 bytes an item weighs on disk and the kilobyte it weighs in the cache. Keys, pointers and serialization overhead are the term people leave out, and leaving it out is how a cache that was sized to fit does not.

The general shape is worth keeping: if the working set fits, the cache is a hit-rate problem; if it does not, it is a sharding problem, and no amount of eviction tuning converts the second into the first.

Estimation 6 — ID generation rate

The shortest estimate here, and it teaches the most under-used outcome of all: proving that something is not worth designing.

Assumptions. They come from the Snowflake identifier scheme, in which each machine stamps every identifier with a millisecond timestamp plus a 12-bit counter that resets each millisecond:

Both are exact by construction rather than guessed, which is why this estimate has no dominant soft assumption at all.

Arithmetic.

Snowflake: 12 bits of sequence per millisecond per node
   4,096 /ms x 1,000 ms/s  =  4.1 M IDs/s/node
   4.1 M/s x 1,024 nodes   =  4.2 B IDs/s

Finding: four billion IDs per second is roughly 42,000x the 100,000/s peak this track’s busiest designs assume. That is 4.19e9 / 1e5 per second — note that 1e5 here is a rate, not the seconds-per-day constant used elsewhere in this chapter.

Even against the 1 million messages a second assumed in ch 20 it is 4,190x. That is the answer to “will the ID generator be a bottleneck?” — it will not, by four orders of magnitude, so say so and spend the time elsewhere. The full layout is derived in Data model the 64 bit layout derived.

When an estimate comes out four orders of magnitude clear, the correct output is a sentence, not a design. Recognizing that early is worth more minutes than any single calculation in this chapter.


5. The estimation checklist

Everything above compresses into the order you actually execute it in — plus the one skill the six drills could not teach on their own: deciding whether an answer is plausible when there is nothing to compare it against.

flowchart TD
    A["What am I sizing?"] --> B["State the assumption OUT LOUD<br/>DAU, actions/day, bytes/item"]
    B --> B2["Give each one a plausible low and high<br/>Widest ratio owns the answer — say which"]
    B2 --> C["Round to 1 significant figure"]
    C --> D["Compute per-day, then / 1e5 for per-second"]
    D --> E["Apply a STATED peak multiplier<br/>2x smooth · 3x bursty · none if queued"]
    E --> E2["Reporting a fleet or a bill?<br/>redo at 86,400"]
    E2 --> F["Apply replication and retention"]
    F --> G{"Does the answer<br/>change the design?"}
    G -->|no| H["Say so and move on.<br/>Do not polish it"]
    G -->|yes| I["Name the constraint it just exposed"]

    style B fill:#1d3557,color:#fff
    style B2 fill:#1d3557,color:#fff
    style G fill:#bc6c25,color:#fff
    style I fill:#2d6a4f,color:#fff

The colour key is local to this diagram — nothing in this flowchart is a component, every box is a step you perform, so ch 01’s colour key for the track’s architecture diagrams does not apply. Here, blue marks the two boxes that are about saying something out loud, which are the two people skip; orange marks the one real decision; green marks the output of the whole procedure. The seven uncoloured boxes are mechanical.

Walk the diagram once, box by box. Each is a rule one of the sections above derived, and the arrows are the order to apply them in.

  1. “What am I sizing?” Naming the template — requests per second, stored bytes, or bandwidth — is what stops you computing something nobody asked for.
  2. “State the assumption OUT LOUD.” This is the box candidates skip and the one that costs them the round, since an unspoken input makes every number after it unfalsifiable.
  3. “Give each one a plausible low and high.” This turns a list of assumptions into a sensitivity analysis. It is Every estimate is a chain of assumptions’s whole result, it costs about ten seconds because you are ranking ratios rather than computing anything, and skipping it is how a candidate produces a number without ever saying what the number is really about.
  4. “Round to 1 significant figure.” A chain with several soft factors in it cannot support more precision than that.
  5. “Compute per-day, then / 1e5 for per-second.” The shortcut of Rounding discipline, and how the arithmetic stays doable in your head.
  6. “Apply a STATED peak multiplier.” Capacity is bought for the busiest second, and the multiplier is an assumption like any other.
  7. “Reporting a fleet or a bill? Redo at 86,400.” The instant the number leaves your mouth as a result rather than as a thought, redo the division exactly.
  8. “Apply replication and retention.” The two multipliers that turn a daily byte count into a fleet-sized one.
  9. “Does the answer change the design?” The only box that is a question about you rather than about arithmetic. If no, say so and move on — an estimate that would lead to the same architecture at 10 TB and at 100 TB has already told you everything it can. If yes, name the constraint it just exposed, because that name, not the number, is the output of the whole procedure.

The most common failure is not arithmetic — it is computing a number nobody needed. If the storage estimate would not change your design whether it came out 10 TB or 100 TB, do not compute it; say “storage is not the constraint here, the read QPS is” and spend the minute there instead.

The second most common failure is silently inventing an assumption. “500 M DAU” is fine. “500 M DAU” said in your head and never out loud is not, because when the interviewer’s mental model says 5 M, every number after that is wrong and neither of you knows why.

Enumerating the candidate constraints

The last box of the flowchart says “name the constraint”, and Estimation 3 hands you four real ones — file descriptors, kernel memory, TLS handshake CPU, blast radius. But a list of four answers is not a method, and the next tier you size will have different ones.

The method is a fixed walk, short enough to say under pressure, and you run it every time regardless of which resource the question asked about. Each line is a candidate resource, the arithmetic that sizes it, and the ceiling you compare against:

CPU                      cycles per request x QPS, against cores
memory                   working set x bytes/entry, against RAM per box
network bytes            QPS x bytes/response, against the network card
disk bytes               items/day x bytes x retention x replication, against capacity
disk IOPS                random reads/s, against 100/s per spindle or 500 k on NVMe
descriptors/connections  concurrent sockets, against the per-box ceiling
blast radius             what one machine's death takes with it

Seven lines, each of which is one of the templates already drilled, pointed at a different resource. The binding constraint is whichever your arithmetic exhausts first — and it is very often not the one the question named, which is precisely the shape of Estimation 3, where the memory answer was right and irrelevant.

The last two lines are the ones people omit, because they are the two that are not throughput at all. A descriptor limit is a configured ceiling rather than a physical one. Blast radius is a reliability budget that can force a fleet three times larger than any capacity number justifies.

Walking all seven costs about fifteen seconds and is the difference between “the answer is 3 boxes” and “memory says 3, and here is what actually binds”.

Sanity-checking a number you cannot verify

At some point you will have an answer, no way to look it up, and thirty seconds. You will never have a reference figure to compare against in an interview, so the check has to come from somewhere other than the source of the number. Four methods cover almost every case, and each takes one line.

Recompute it by a different route. The strongest check is arithmetic that shares no step with the original. Estimation 1 reached 275 PB through a five-year daily total; the check goes 50 TB a day is about 18 PB a year, five years is 90 PB, three copies is 270 PB. Two routes that agree are very unlikely to be wrong in the same direction, and the whole check took eight seconds.

Convert the answer into a physical object. Units like the exabyte and the petabyte carry no intuition, so trade them for something that does: drives, racks, buildings, dollars, people. 1.1 EB became about 55,000 drives and, at ~600 drives to a fully populated rack, about 90 racks — one row of a data hall. $2.2 M a day becomes $800 M a year, which is a number you can compare against a company’s revenue.

If the object is absurd — four buildings for a startup, or one machine for a global service — the arithmetic is wrong or an assumption is. Carry the conversion constant, not just the unit: “racks” only checks anything if you know how many drives are in one, and it was the missing 600 that made “a thousand racks” sound reasonable.

Check the answer against a bound you already believe. You know a commodity machine has tens of gigabytes of memory, that one database primary handles thousands of writes a second, and that a human types a few characters a second. Any estimate that quietly requires a machine to do a hundred times more than one of those bounds is wrong, and you can say which bound it violated.

Check the direction of every ratio before the magnitude. Reads should outnumber writes on a feed. Media bytes should dwarf text bytes. Storage should grow and never shrink. A peak should exceed an average.

A ratio pointing the wrong way is a units slip or an inverted division, and it is far more common than a mis-multiplication. Fix the direction first, because a number with the wrong sign of reasoning behind it cannot be repaired by better arithmetic.

The failure these guard against is a specific one, and it is worth naming so you can hear yourself doing it: the units slip. Megabytes written where gigabytes were meant. Bits where bytes were meant — a bitrate in megabits divided by 8 is a byte rate, and forgetting that division is a factor of eight. A per-day figure compared against a per-second capacity.

Every one of those produces a confident, well-formatted, thousand-fold-wrong answer, and none of them is caught by redoing the same multiplication more carefully. Carry the units on every line, the way each block above writes 1.1e17 B before it writes 110 PB, and the slip becomes impossible rather than unlikely.


6. Numbers worth memorizing cold

Every constant the chapter spends is gathered here in one place, so you can drill them. Everything here is either a physical fact, an exact conversion, or a stated default — and where it is a default rather than a fact, the row says so, because a default silently taken is how two people estimating the same system reach different fleet sizes.

QuantityValue
Seconds per day86,400 -> 1e5 to reason, 86,400 to provision
Seconds per month2.6e6 -> 2.5e6 to reason (the one place two figures are kept: 3e6 would be 16% high)
1 M/day10/s via 1e5 · 11.6/s exact
1 B/day10,000/s via 1e5 · 11,574/s exact
Memory reference100 ns
SSD random read100 us latency. Device throughput is 500 k-1 M IOPS at depth, not 1e4
Datacenter round trip500 us
Disk seek (HDD only)10 ms -> 100/s per spindle. An SSD does not seek
Same-continent RTT~30-80 ms
Transatlantic RTT, US-East <-> Europe~75 ms — the short ocean crossing. It sits inside the same-continent band above, so the band alone will not tell you whether an ocean is involved: name the route, not the number (Latency numbers and what each one forbids)
Cross-continent RTT, California <-> Europe150 ms — a different route from the row above, not that row doubled: it crosses the continent before it crosses the ocean
2^324.3e9
2^41 ms69.7 years
Char (ASCII, the basic Latin character set)1 B
UUID (universally unique identifier)16 B binary, 36 B as text
Typical web page~2 MB
1080p video bitrate~3-5 Mbps. Default to 3 Mbps for an average across devices and say so
CDN egress~$0.02/GB
Commodity box64-256 GB RAM, 8-64 cores, 1-10 Gbps network card. Default to 128 GB / 16 cores / 10 Gbps and say so — silently taking an end of a range is how two chapters reach opposite fleet sizes
Hard drive~20 TB per drive. A stated default, and it moves with the year
Rack of drives~600 drives fully populated (42U of 4U, 60-bay enclosures). A stated default — it is what turns a petabyte count into an object you can picture
Cache read, same datacenter~0.5 ms, which is mostly the round trip and not the lookup
Database read, indexed row~10 ms on a loaded primary; ~1 ms on SSD with a warm buffer pool. A stated assumption, not a hardware constant — it is not the 10 ms disk-seek row above

The rows below are workload defaults rather than physical facts. They exist because the hardest part of a first estimate is not the arithmetic but inventing an input you can defend.

Every one of them is a default to be stated out loud and abandoned the moment a real number appears. Take one silently and you have committed exactly the error the commodity-box row warns about. They are order-of-magnitude anchors, and a product that is genuinely 3x off any of them is a product whose owner already knows the real figure.

Workload defaultValue
DAU as a fraction of registered users10-30% for a consumer product, default 20%. A social feed sits high, a utility app low
Sessions per user per day (consumer)3-10, default 5. This is the number the QPS estimate is usually most sensitive to
Creation actions per user per day0.05-1, default 0.1 — people read one to two orders of magnitude more than they write, which is where the 50:1 to 100:1 read:write ratio comes from
Concurrent connections per box (long-lived sockets)100 k-500 k. A stated default, and a 5x range that sets a connection tier’s fleet size outright — memory does not (Estimation 3)
B2B tool (software sold to companies, not consumers)DAU 50-70% of seats (people use work software on work days), 1-2 long sessions/day, and a peak multiplier of 3x or more rather than 2x, because the day’s traffic lands in an 8-hour window in a handful of time zones instead of spreading across 24

7. Interviewer pushback

Four challenges are what this material actually draws, and each is answered below the way it should be spoken. Every answer names an assumption and its consequence rather than defending a number.

“Where did 500 million daily actives come from?”

I made it up, and I said so — it is a placeholder that makes the arithmetic concrete. What matters is the sensitivity: at 50 M this fits on a few dozen machines and the design is boring; at 5 B the storage tiering and multi-region become the whole conversation. If you have a real number I will redo it, but the shape of the design only changes at order-of-magnitude boundaries.

“Your seconds-per-day is 16% off.”

Deliberately. My input assumptions are wrong by at least 2x, so carrying 86,400 through would be false precision. If we get to a point where 16% changes a decision, that is the moment to use real numbers — and it usually means we are sizing a fleet for cost, not deciding an architecture.

“You said three boxes for 20 million connections. Would you deploy that?”

No, and that gap is the useful part of the estimate. Memory is not what sizes a connection tier — descriptors, TLS handshake CPU, and failure blast radius are. One box holding 6.7 M connections means a single restart reconnects 6.7 M clients simultaneously, which is a self-inflicted thundering herd — a flood of simultaneous identical requests that the system caused itself. I would target 100-500 k per box, so 40-200 boxes, and now the interesting design question is the reconnect-storm policy, not the RAM.

“Do I need the peak multiplier if I have autoscaling?”

Yes, for two reasons. Autoscaling reacts on the order of minutes and traffic spikes on the order of seconds, so the peak sizes your headroom, not your steady state. And the stateful tiers — databases, connection holders, anything with a warm cache — cannot scale on that timescale at all. The multiplier is really a question about which tiers can absorb a spike and which have to be provisioned for it.


Cheat sheet

This is the chapter compressed to what you would want in front of you five minutes before an interview.

Four templatesQPS = DAU x actions / 1e5 · storage = items/day x bytes x days x replicas · bandwidth = QPS x bytes/response · memory = working set x bytes/entry
Defaults when you have nothingDAU 20% of registered · 5 sessions/day · 0.1 creates/user/day · 128 GB/box · 20 TB/drive · 600 drives/rack. Say every one of them out loud
The key approximation86,400 -> 1e5 to reason, 86,400 to provision. Say which
Peaka stated assumption: 2x smooth, 3x bursty, none if a queue absorbs it. Sizes headroom, not steady state
The three latenciesmemory 100 ns · datacenter round trip 500 us · cross-continent 150 ms
Cache leverageDB load QPS x (1-h) falls linearly to zero; the reduction factor 1/(1-h) diverges. Each extra nine divides load by ten. “Linear in h” and “linear in 1-h” are the same claim, so neither is the point
Binding constraintwalk CPU · memory · network · disk bytes · disk IOPS · descriptors · blast radius, and take whichever runs out first
Every estimate is a productso a factor that is 2x wrong makes the answer 2x wrong. Rank assumptions by their high-to-low spread, and name the widest one out loud
Robustnessan estimate is sound when its conclusion survives its own uncertainty, not when its number is accurate
Sanity checkredo it by a different route · turn it into drives, racks or dollars · test it against a bound you already believe · check every ratio points the right way
The real outputnot a number — the name of the binding constraint
Biggest mistakecomputing something that does not change the design
Second biggestan assumption you never said out loud
Thirda units slip: bits for bytes, MB for GB, per-day against per-second. Carry the units on every line

Next: 03 — A Framework For System Design Interviews — where these numbers go in the 45 minutes.