Design a URL Shortener (TinyURL / bit.ly)
mediumA URL shortener looks like a hash map with a web server in front, and interviewers use it precisely because that first impression is wrong. The storage is trivial. The interesting parts are key generation under concurrency, a read path two orders of magnitude hotter than the write path, and what "delete" means once a link is in someone's SMS history.
Key Concepts
High-level design
Write path: client → API gateway → URL service → key generator → KV store → return short URL.
Read path: client → CDN/edge (cache check) → API gateway → URL service → KV store → 302 redirect.
Async: click events → Kafka → aggregator → analytics store.
Abuse: URL submission → safe browsing check → tag/block as needed.
Edge: CDN caches both the create response and the redirect.
Components
- API gateway with rate limiter (per IP, per API key).
- Key generator service (counter or hash).
- KV store (DynamoDB, Cassandra, or sharded Postgres) keyed by short alias.
- Edge cache (Cloudflare, Fastly) on the 302 redirect.
- Redis warm-tier cache for very hot links.
- Kafka + analytics pipeline (Spark / Flink → ClickHouse).
- Abuse / phishing filter (Safe Browsing API).
- Admin tools: revoke, expire, search.
Capacity, worked
Assumptions stated so you can substitute your own.
New links per day 10,000,000 Redirects per link over its life 100 Retention 5 years --------------------------------------------------------- Writes 10M / 86,400 = 116 per second Writes at 3x peak = 350 per second Reads 1B / 86,400 = 11,600 per second Reads at 3x peak = 35,000 per second
Row: key 7 B + URL ~200 B + owner, timestamps ~100 B
= ~310 bytes
Rows in 5 years 10M x 365 x 5 = 18,250,000,000
Raw storage 18.25B x 310 B = ~5.7 TB
With indexes and replication (x3) = ~17 TBKey space, 7 chars base-62 62^7 = 3.5 x 10^12 keys Used after 5 years = 0.5% of space
What the numbers decide:
- 35k reads/s is a cache problem, not a database problem. At a 95% hit
rate the database sees 1,750 reads/s, which one replica handles.
- 5.7 TB does not need exotic storage. It needs one sharded table.
- 0.5% key usage means 7 characters is right; 6 (56 billion) would be
tight, 8 wastes a character on every link ever printed.
Key generation compared
Three options and the cost of each.
Hash + truncate (MD5/SHA, take 7 chars)
Collision odds rise with sqrt(key space): ~1.9M keys before collisions
become likely. At 10M/day that is under two months.
Needs: read-before-write on every insert.
Gets you: the same short link for a repeated URL, which users like. Random 7 chars, check existence
Needs: read-before-write, same as above, but no clustering under load.
Gets you: unguessable keys. Counter, base-62 encoded
Needs: nothing per write. Uniqueness is structural.
Costs: keys are enumerable in creation order.
Fix: encode the counter through a bijection (e.g. multiply by a large
coprime modulo 62^7), or hand each writer a block of 10,000 IDs so the
global sequence is never visible. Distributed counters without a single point:
- Zookeeper/etcd hands out ranges: one round trip per 10,000 links.
- Per-server prefix (server 03 owns keys starting 03) removes
coordination entirely at the cost of one character.Failure modes in production
Hot key after a viral share
Symptom: one shard's CPU saturates while the rest idle.
Cause: traffic concentrates on a single key, and sharding spreads keys,
not requests to one key.
Fix: edge cache with a short TTL; the origin then sees one request per
edge per TTL rather than the full flood.
Cache stampede when that key expires
Symptom: latency spike exactly at TTL boundaries.
Cause: every edge misses simultaneously and asks the origin at once.
Fix: jittered TTLs, and serve-stale-while-revalidate. Expired key reissued to a new owner
Symptom: users following an old poster or screenshot reach a stranger's
destination.
Cause: keys returned to the free pool on expiry.
Fix: tombstone permanently. The key space is 3.5 x 10^12; you are not
short of keys. Open redirect abuse
Symptom: your domain appears in phishing filters.
Cause: anyone can point your trusted domain at anything.
Fix: scan destinations on write, re-scan asynchronously, and keep a
takedown path that responds in minutes rather than days.