The whole walkthrough, written out
Every step below is the same text the guided version shows, in order. The simulations are what make it worth doing rather than reading — but the reasoning is here either way.
1. What are you actually being asked for
Requirements
The prompt is deliberately underspecified, and the first minutes are for narrowing it. The questions worth asking are the ones whose answers change the design: how long do links live, can they be edited, do we need analytics on clicks, is a custom alias supported, and what happens when someone requests a code that does not exist.
For this exercise: links are permanent, immutable, no custom aliases, and click analytics are a separate asynchronous concern. That last one matters more than it sounds — putting analytics on the redirect path would double the work on the hottest endpoint in the system.
The constraint that shapes everything is the read/write ratio. A hundred million writes a day against ten billion reads is 100:1, and a system with that ratio is a caching problem wearing a database costume.
100:1 reads to writes. This is a read-path problem.
2. The numbers, out loud
Estimate
Ten billion redirects a day over 86,400 seconds — call it 100,000 — is about 115,000 redirects a second on average. Peak is three times that, so 350,000 a second. Writes are a hundredth of the reads: 1,150 a second average, 3,500 at peak.
Storage: a row is a short code, a long URL, an owner and a timestamp. Call it 500 bytes with indexes. A hundred million a day is 50 GB a day, 18 TB a year, and roughly 55 TB a year across three replicas.
That storage number tells you this cannot live on one machine for long, and the QPS number tells you the read path cannot touch a database at all.
350k reads/s at peak, 3.5k writes/s, ~55 TB a year replicated.
3. The design you would draw first
A first design
A load balancer, a stateless redirect service behind it, and a database holding the mapping. This is the right shape and it is what a competent person draws in ten minutes.
The numbers below are scaled down so the simulation runs in a minute of virtual time — four thousand requests a second rather than three hundred and fifty thousand — but the ratios are the ones you just calculated. Everything you are about to see happens at both scales.
Run it at this load. What gives way first?
The database — every redirect is a query against it — The service has 192 concurrent slots at 4ms each, which is around 40,000 requests a second. The database has 32 slots at 6ms, which is under 5,000. The bottleneck is whichever component has the least capacity, and it is almost never the one doing the most visible work.
4. Now turn the traffic up
Watch it break
Take the traffic multiplier to three and watch. The database utilisation bar goes past the 70% line, then past 90, and the p99 latency chart — which is on a log axis — starts climbing far faster than the load does.
This is the utilisation curve doing exactly what it always does. The database was at 85% at the designed load, which felt fine. At three times that load it is not three times worse; it is unbounded, because the queue never drains.
Note what does NOT help: adding replicas to the redirect service. It was never the constraint, and doubling something that is 10% utilised changes nothing at all.
The database saturates at roughly 4,700 requests a second, and past that the wait is unbounded.
5. What the numbers say to do
Find the bottleneck
Every redirect is a lookup of an immutable value by a key. That is the most cacheable thing a system can contain: it never changes, so there is no invalidation problem, and it is looked up by exact key, so there is no query to plan.
The remaining question is what hit ratio to expect, and that depends entirely on how popularity is distributed. Link traffic is heavily Zipfian — a handful of links carry an enormous share of clicks — which means a cache holding a small fraction of the keyspace serves most of the requests.
Run the cache concept beside this and watch the curve. Caching one per cent of a Zipfian keyspace already delivers most of the hit ratio that caching forty per cent would.
A cache over 1% of the keyspace serves the large majority of redirects.
6. One component, and run it again
Change one thing
A cache in front of the database, at a 92% hit ratio. Nothing else changes: same service, same replica counts, same traffic.
The database now sees 8% of the reads, which at the designed load is under 400 a second against a capacity of 4,700. It has gone from 85% utilised to under 10%, and the p99 has collapsed because almost nothing queues anywhere.
Turn the traffic up again. It now takes roughly ten times the load to reach the same trouble, and when it does, the thing to notice is that the bottleneck has MOVED — which is what capacity planning actually is: finding the next constraint, over and over.
The same design takes roughly ten times the load, and the bottleneck has moved.
7. What an interviewer would ask next
What gets asked next
"What happens when a cache node dies?" A third of your keyspace becomes a miss at once, and the database takes a load step it was not sized for. Consistent hashing limits how much of the keyspace moves; a warmed standby limits how long it hurts.
"What happens at midnight when a batch of TTLs expires together?" That is a cache stampede, and the answer is request coalescing plus jittered TTLs — or, for immutable data like this, no TTL at all.
"How do you generate the short codes?" A counter with base-62 encoding, sharded so no two nodes issue the same value. Random codes need a uniqueness check, which is a read on the write path, which you have just spent the whole exercise trying to avoid.