deep dive 1 Key generation
Every new link needs a code that no other link already uses — 400 of them a second at peak, and the plan says 15B links after five years. Whatever generates them must make a duplicate impossible, not just unlikely: if two links share a code, one visitor silently lands on the wrong page, and no error shows up anywhere.
Before choosing how to generate codes, choose how long the code is, because length sets the size of the space we pick from. Codes are written in base62: counting with sixty-two symbols instead of ten — the digits 0–9, then a–z, then A–Z — which packs the largest number into the fewest URL-safe characters. Six characters give 62⁶ ≈ 57B possible codes: the plan fits, but the space is already 26% full by year five — and for randomly generated codes, that also means roughly one in four new codes would hit an existing one by then. Seven characters give 62⁷ ≈ 3.5T codes, and the same plan fills less than half a percent of it. One extra character removes the problem entirely, so the code is seven characters.
The tempting one is to hash the long URL and keep seven characters of the result. The same URL always produces the same code, so someone submitting the same URL twice gets the same link for free, and there is nothing to coordinate. This is where most engineers start, and at full hash length it is a fine idea. The problem hides in “keep seven characters.” A full hash almost never collides, but a truncated one does — and the birthday bound says how soon. In a space of 3.5T, random values start colliding at even odds around the square root of the space: about 2M codes, not trillions. We plan for 15B links, so truncated-hash collisions are not a risk, they are a certainty — and every one of them quietly sends someone's visitors to someone else's page, with no error anywhere. Free deduplication is not worth an undetectable wrong-page bug. Hashing is out.
That leaves two live options, and the real choice between them is simple: pay a small coordination cost once, or pay an existence check on every create, forever.
Random codes generate seven random base62 characters. Any app server does this alone — nothing shared, nothing coordinated — and the codes can't be guessed, so nobody can walk through your link space. But random means collisions are only unlikely, not impossible, so every create must first ask the store “is this code taken?” — a read on your busiest write path — and retry in the rare case the answer is yes. You can make that check nearly free with a bloom filter, but it never goes away, and past 2M links the retry path has to actually work.
A counter keeps one number that goes up by one for every link ever created; a link's code is that number, written in base62. This makes uniqueness automatic — the counter never produces the same number twice, so no two links can ever share a code, and nothing needs to be checked. There is no existence read on the write path at all. The cost is that a counter is one shared thing that some service has to own, and that codes handed out in order are guessable: from one code, anyone can guess the next code exists and walk your links one by one.
Counter → base62. When one option's cost can be engineered away and the other's is permanent, take the first. The random option's existence check is a read 400 times a second on the write path, forever, bloom filter or not. The counter's coordination cost, as the mechanics below show, can be made almost zero — and its guessability is fixed by scrambling the number before it becomes a code. Uniqueness stops being a probability and becomes arithmetic: no check, no retry, no read on the write path. And the space lasts: 3.5T codes at 400 creates a second — the peak, held forever, which real traffic never does — runs out in roughly 300 years.
Under the hood — how it works
A counter is just a number. The first link ever is number one, the next is number two, and link number one billion gets the code 15FTGg — that is one billion written in base62, six characters (shorter numbers are padded with leading zeros so every code is seven characters long).
The counter itself is one small shared sequence — a database sequence, or a tiny counter service. It is the only shared thing in this design's write path, so it is worth being precise about how it is used. App servers do not ask it for every link. Each server asks once for a block of 100k numbers, then hands them out one by one from memory, with no network call at all, and asks for the next block when the current one runs out. At 400 creates a second spread over four servers, one block lasts a server about 20 minutes — so the shared sequence is contacted a few times an hour instead of 400 times a second. That is why the coordination cost is close to zero in practice.
What happens when a server crashes in the middle of a block? The numbers it never used are simply never used by anyone. That is fine: codes have to be unique, not consecutive, and a gap of a hundred thousand numbers costs nothing in a space of 3.5T.
One follow-up is worth raising yourself: custom aliases. Real shorteners let a paying user pick the code — sho.rt/summer-sale instead of a generated string. This does not change the generator; it adds a second, much smaller path beside it. A chosen name has no counter behind it, so it needs exactly what the random option needed — check the store, refuse if the name is taken. That check was too expensive at 400 generated codes a second, but people rarely pick custom names, so here the check is cheap. Two details make it real: keep a reserved list (www, api, admin, and every path this product itself serves), and keep the two kinds of code from ever fighting over a name — generated codes are always seven characters, so requiring aliases to be any length except seven settles it for free.
Prep material often teaches this decision in a different shape: a key generation service, KGS for short. The idea is to make codes ahead of time — a separate service fills a table with unused random codes, and when someone creates a link, a code is taken from that table and marked used. It works, and it deserves a straight answer rather than a shrug: it solves the same problem as the counter's blocks, with more machinery. The table of codes is one more store to run, back up, and fail over; two app servers must never grab the same code, which is the same coordination the counter already solved; and the service itself is one more thing that can die on the write path. Notice that the block lease above is already pre-generation — a leased block is a batch of promised codes — just without the second database. If an interviewer asks for KGS, describe it, then say why the block lease buys the same guarantee cheaper.
There is a second, less obvious problem with serving codes in counter order: the codes themselves reveal your company's numbers. Anyone can create one link today and another tomorrow, then subtract the two code values — the difference is exactly how many links everyone created in between. That is your growth chart, readable from the outside. (Estimating production from serial numbers is an old trick; the well-known case is the German tank problem, where Allied statisticians estimated tank production from captured serial numbers.) So scramble the sequence before it becomes a code. The scramble must map every number to exactly one code and back — that is what format-preserving encryption does. Do not use a hash for this: two numbers can hash to the same code, which brings back the very collisions the counter was chosen to rule out.
The German tank problem — estimating output from serial numbers ↗