TL;DR: Storage is the trivial part; read volume and analytics decide the design. Use a counter-driven base62 key, serve redirects like a CDN problem, emit click events asynchronously, and pick 302 with an explicit no-store cache policy when each redirect must reach your service, accepting a per-click hop as the price.
How to approach it
Pin three numbers before drawing anything: writes per year, read-to-write ratio, and whether clicks must be counted. Then say which constraint dominates, because the standard diagram is drawn for a read-heavy service and the answer changes if writes or analytics dominate instead.
A strong answer
Numbers first. Assume 500 million new links a year at roughly 500 bytes per row including metadata: about 250 GB annually, barely a terabyte after years and indexes. Storage never decides this design; reads do. Shorteners see 100:1 or higher read-to-write, so a service creating 500M links a year realistically serves billions of monthly redirects, at 100 redirects per new link that is 50 billion per year, about 1,585 RPS average. A 10k RPS design point is about 6.3 times that mean; choose further campaign headroom explicitly. Design the redirect path like static asset delivery, because it is one.
Key generation. A monotonic counter encoded base62 needs seven characters for 62^7, about 3.5 trillion keys. Counters beat hashing the URL because there is no collision path to handle; the cost is distributing the counter, solved by handing each app node pre-allocated ranges of, say, 100,000 keys rather than calling a central sequencer per write. Sequential codes are guessable, which is fine for public marketing links; private links get a random token plus permission checks at redirect time.
Redirect semantics, where the marks are.
| 301 permanent | 302 temporary | |
|---|---|---|
| Browser caching | Heuristically cacheable unless controlled | Can cache with explicit freshness |
| Click analytics | Cached redirects bypass origin events | Use Cache-Control:no-store for origin observation |
| Backend load | Falls over time | Constant |
| Right choice | Links that never change or expire | Anything you might retarget, measure, or kill |
Use302 with explicit Cache-Control when retargeting or observation requires it because retargeting and measurement are the product. State the reversal unprompted: purely static links with no analytics mandate can go 301 and hide behind edge caching.
Serving path. Stateless app tier behind a load balancer, Redis cluster holding short-code lookups, misses falling through to a read replica hitting a unique index. At 10k redirects per second with a 95 percent cache hit rate, misses are 500 point lookups per second, comfortable for Postgres or MySQL. At bit.ly scale the mapping lives in an edge key-value store so redirects resolve near the user.
Analytics off the hot path. Redirect handlers append a click event (code, timestamp, coarse geo, referrer) to Kafka and return immediately; rollups land in a columnar store seconds later. Nobody who has operated one of these performs the analytics write inside the redirect request.
Also name the boring essentials: malware and phishing scanning before a link activates (shorteners are abuse infrastructure), per-client rate limits on creation, custom domains, and deletion that propagates a tombstone to the edge within cache TTLs.
What interviewers probe next
"Links must expire after 24 hours. What changes?" 301 dies instantly: a cached permanent redirect keeps working past expiry and cannot be recalled. Use explicit cache directives and expiry checks, ensuring any edge/browser freshness does not outlive the link.302alone does not prevent caching; no-store is appropriate when every click must be observed. The memorised diagram omits this mutation entirely.
"Where do keys come from if a datacenter is isolated?" Range allocation with generous block sizes rides through hours of isolation; two islands never issue overlapping ranges.
"A custom alias collides with a counter code." Same table, same unique index; alias creation reserves its code transactionally and conflicts return a clear error rather than silent suffix mangling.
Common mistakes
Drawing the six-box architecture (client, LB, app, cache, DB, analytics) with no numbers attached, which reads as rehearsed rather than reasoned.
Choosing 301 everywhere and then promising real-time click dashboards, a contradiction in one breath.
Skipping abuse handling entirely; a shortener that happily wraps phishing links gets domain-blocklisted by browsers and email providers, which kills the product.
References