Basis Lab

A System Design in Forty Minutes

What a One Line Design Request Actually Asks For

A one line request becomes designable once you have written the one job, the two or three operations with their input and output shapes, and the things you are leaving out.

The request arrives as one line. Build a service that turns long web addresses

into short ones, and tell us how often each short one is clicked. That is a

product description, and nobody can design from it, because it says nothing

about how many, how large, how fast, or what happens when the same thing is

asked twice. The first five minutes turn it into something a drawing can be

checked against.

FIG 1The order the scope gets written in
The two branches leaving the main line are the two ways this stage fails. Both cost time later rather than now, which is why neither feels like a mistake at the time.

The one job

A link shortener looks like two products and is one. Ask what has to keep

working for any of it to be worth paying for, and the answer is that a code

handed to the service comes back as the address it stands for. If that stops,

every link ever handed out is dead and the analytics are a record of a service

nobody can use. If the click counting stops, the links still work and somebody

files a ticket.

So the one job is to return the address a code stands for, correctly, for as

long as the code exists. Creating codes serves that. Counting clicks is a

second, smaller product sharing a key with the first, and ranking it second

rather than equal is what later allows a weaker promise about it without that

feeling like a concession.

The operations, written as shapes

Two or three operations, each with what goes in and what comes out. Writing them

as signatures rather than as verbs is the whole trick, because a verb cannot be

multiplied by anything.

FIG 2The three operations, as shapes rather than as verbs
python
def create(target: str, owner_id: bytes) -> str:
    """target is a URL, and RFC 3986 bounds its length nowhere, so the mean
    length is an estimate rather than something to look up. owner_id is 16
    bytes of fixed width. The return is 7 characters out of a 62 character
    alphabet, and that 7 is derived below rather than remembered."""
    ...

def resolve(code: str) -> tuple[str, int]:
    """code is 7 bytes. The return is the target and the HTTP status to
    redirect with. A 302 keeps the client coming back, so every click is
    counted; a 301 lets the client remember, so later clicks never arrive."""
    ...

def clicks(code: str, window: tuple[int, int]) -> list[tuple[int, int]]:
    """window is two timestamps, 8 bytes each. The return is one count per
    time bucket, so the answer grows with the width of the window and not
    with how popular the link is."""
    ...
The note on the resolve return value is a scoping decision disguised as a status code. The permanent redirect hands most of the read traffic to the clients and removes it from the estimate entirely, a larger change than any component in the drawing.

The third signature deserves a second look. It returns one count per bucket

rather than one row per click, so its answer is bounded by the window the caller

asked for. An operation whose output grew with the popularity of the link would

have no bound at all, and that difference stays invisible until somebody writes

the output shape down.

How long the code has to be

Here is the step almost everyone skips. The code length is quoted from memory as

six or seven characters, and it is forced by how many distinct links the service

must name over its whole life. Let LL be that total, aa the alphabet size and

nn the code length. The rows below use the hundred million links a day that the

next stage derives, which is the one figure here borrowed rather than worked

out.

FIG 3The code length is a logarithm, not a division
how many distinct links the service must name over its whole life
how many characters the alphabet has, 62 for digits with upper and lower case letters
the code length in characters, which must be a whole number, so round up
how many distinct codes of that length exist, the quantity that has to cover L
The wrong turn is to compute L divided by a, which answers a question nobody asked and gives a number far too small. Each extra character multiplies the space by 62, so the length grows like the logarithm of the demand and is insensitive to being wrong about L.
FIG 4What each extra character buys
stepcode lengthdistinct codes availablemultiple of the row abovewhat happened
159161328321Nine hundred and sixteen million. Nine days at a hundred million links a day, then the service is out of names.
265680023558462The row above times 62. Fifty-six point eight billion, which is a year and a half, not a service life.
37352161460620862Three and a half trillion, about ninety-six years at that rate. This is the answer.
4821834010558489662The eighth character costs every user a keystroke forever and buys sixty-two times more room than can ever be used.
4 steps
Because the demand falls between two rows differing by a factor of sixty-two, being wrong about the total number of links by a factor of ten does not change the answer. That is why one significant figure is enough for the estimate feeding it.

Seven, then, with the reason on the page rather than in anybody's memory. The

same arithmetic handles a shrinking alphabet, say because codes must survive

being read aloud and the letters resembling digits have to go. With 32

characters and LL at 1.8×10111.8 \times 10^{11}, the ratio 25.9/3.4725.9 / 3.47 is 7.5, so

eight characters. One keystroke is the price of being readable.

Who is asking, and how many of them

Three populations, not one. Those that create links are a small set, because

creating one is deliberate work done by a publisher or a marketing tool. Those

that follow links are everybody who sees one, which is the publisher's audience

rather than the publisher. Those that read click reports are a subset of the

first, asking rarely and asking for far more work per question.

FIG 5Everything the request might have meant, against four tests
the one job fails without itit changes an estimateit adds a boxthe request asked for it
resolve a code to an address1111
create a code for an address1111
clicks per code over time0111
custom vanity aliases0010
expiry dates on links0010
accounts, teams and sharing0010
malware and phishing checks0110
A one means the heading is true of that candidate, a zero that it is not. Only the two marked rows have anything in the first column, and only the top three have anything in the last. The bottom four add a box each and nobody asked for any of them. At roughly four minutes to estimate, draw and defend one box, carrying all four spends sixteen of the forty minutes on work never requested.

The gap between the second population and the first is the most useful number in

the whole scope, and it is a ratio rather than a total. A service whose reads

outnumber its writes by a hundred to one is a different object from one where

they are equal. Write down that the counts are separate and that the second is

expected to be far larger, and the next stage has something to estimate.

The two ways this goes wrong

The first is designing for a job nobody asked for. Nothing feels more like

competence than volunteering expiry policies, vanity aliases and team accounts,

and each is a box that now needs justifying by a number nobody estimated. The

grid above prices it.

The second is quieter. It is drawing before the operations are written down,

which happens because the components are the memorised part. Such a drawing can

be perfectly reasonable and still indefensible, because the only honest answer

to why a box is there is that systems like this usually have one. A shape can be

multiplied. A box cannot.

The one sentence

Fold all of it into one sentence a drawing can be measured against. It needs a

verb, a volume, a bound and an exclusion.

Store an address under a seven character code and return that address on demand,

for a readership expected to be around a hundred times the set of people who

create codes, fast enough that a reader does not notice the hop, while counting

clicks per code accurately to within a few; aliases, expiry, accounts and

content checking are out.

Every number in that sentence is an estimate rather than a measurement, and the

next stage is where they come from. What the sentence does is make those

estimates necessary, which is the only thing that reliably stops a design

becoming a list of components.

What to hold on to

Name one job and rank the others below it, because that ranking is what later

lets a weaker promise be made about the lesser one. Write every operation as an

input shape and an output shape, because the arithmetic that justifies every box

multiplies shapes and cannot be done on verbs. And say out loud what you are not

building, in the second minute, while it is still free.

Recap

  • A one line product request becomes a design problem only once the one job, the two or three operations with their input and output shapes, and the things being left out have all been written down.
  • The length of an identifier is forced by the logarithm of how many distinct things it must name, taken in the base of its alphabet, so seven characters out of sixty-two name three and a half trillion links while six name only fifty-seven billion.
  • The two populations behind a service can differ by orders of magnitude, and that gap rather than the total traffic is what most of the later design turns out to be answering.

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

NextNumbers Before Boxes →

The rest of this course

  1. 01What a One Line Design Request Actually Asks Foryou are here
  2. 02Estimating the Load Before You Draw Anythingopening only
  3. 03The One Machine Version, and the Number at Which It Breaksopening only
  4. 04Where 360,000 Reads a Second Actually Goopening only
  5. 05Splitting the Writes, and the Number That Decides How Many Piecesopening only
  6. 06What a Reader Sees While Something Is Brokenopening only
  7. 07Justifying Every Box in the Drawing With the Number That Forced Itopening only
  8. 08Where Each of the Forty Minutes Goes, and the Two Mistakes That Cost Tenopening only

Read alongside