URL Shortener
Try it first (15 minutes, paper only). Design a service like bit.ly: create short links, redirect them, and survive 100 million new links a month for 10 years. Write down your requirements, one number for storage, your API, your database choice and why. Then open the solution and mark what you missed.
⏱ Interview mode
45-minute system design round45:00- 1. Clarify requirements5 min
- 2. Estimates5 min
- 3. API and data model7 min
- 4. High-level design13 min
- 5. Deep dives12 min
- 6. Wrap up trade-offs3 min
Show solutionTried it yourself first? Open it to compare and note what you missed.
1. Requirements
Functional
- Create a short link for a long URL (optionally with a custom alias and an expiry).
- Redirect a short link to the original URL.
- Basic click analytics (count, maybe by country).
Non-functional
- Redirects must be fast (p99 under ~50 ms) and highly available. A broken link is worse than a slow create.
- Short codes must be unique and not guessable in sequence.
- Read-heavy: roughly 100 reads per write.
2. Back-of-the-envelope
| Quantity | Estimate |
|---|---|
| New links | 100 M / month ≈ 40 writes/s (peak ~400/s) |
| Redirects | 100× writes ≈ 4,000 reads/s (peak ~40k/s) |
| Links in 10 years | 100 M × 12 × 10 = 12 billion |
| Size per row | ~500 bytes (long URL, code, owner, timestamps) |
| Storage | 12 B × 500 B ≈ 6 TB of data, and with replication and indexes, tens of TB |
The interesting result: throughput is modest, but the data set is large and grows forever. That, not the request rate, drives the database decision.
3. API
POST /api/links { longUrl, alias?, expiresAt? } → 201 { code, shortUrl }
GET /{code} → 301/302 Location: longUrl
GET /api/links/{code}/stats → { clicks, byCountry }
301 or 302? A 301 is cached by browsers, so it is cheaper for you but you lose click counts. Use 302 if analytics matter.
4. Generating the short code
A 7-character code over [a-zA-Z0-9] gives 62⁷ ≈ 3.5 trillion codes, plenty for 12 billion links.
| Option | How | Trade-off |
|---|---|---|
| Hash the URL (MD5 → base62, take 7 chars) | Deterministic | Collisions need a check-and-retry loop |
| Random 7 chars | Simple | Collisions grow with fill; needs an existence check |
| Counter → base62 | A unique number per link, encoded | No collisions. Needs a distributed ID source (ranges handed out per server), and codes must be shuffled so they are not sequential |
A common answer: a range-based counter (each app server leases a block of 1,000 IDs from a coordinator such as ZooKeeper or a DB row), encoded in base62 after a reversible shuffle.
5. Data model and storage
links(code PK, long_url, owner_id, created_at, expires_at)
Access is almost purely by primary key, with no joins. Combined with tens of TB that keep growing, a key-value or wide-column store (DynamoDB, Cassandra) fits naturally and shards by code out of the box. Relational works too, but you will be sharding it yourself at this size. Say this out loud: the storage size, not the QPS, forces sharding.
6. Architecture
Pick a flow and press Play (or use ← →) to watch the request move through the system.
flowchart TB
U([Client]) --> CDN[CDN / edge]
CDN --> LB[Load balancer]
LB --> R[Redirect service]
LB --> C[Create service]
R -->|look up code| RC[(Redis cache)]
R -->|on miss| KV[(KV store<br/>sharded by code)]
C --> ID[ID range<br/>allocator]
C -->|write link| KV
R -. click event .-> K[[Kafka]]
K --> AG[Analytics<br/>aggregator]
AG --> OL[(OLAP store)]1. U → R: GET /aZ3kQ9x arrives through the CDN and load balancer
- Cache the hot links. Link popularity is extremely skewed; a modest Redis tier serves most redirects.
- Record clicks asynchronously. Put an event on a queue and keep the redirect path free of analytics writes.
- Separate the read and write paths so a create spike never slows redirects.
7. Deep dives interviewers push on
- Custom aliases: same table, but a conditional write ("insert if absent") to avoid races.
- Expiry: lazy check on read, plus a background sweeper. Do not delete on the hot path.
- Abuse: rate-limit creates per user and IP, and scan long URLs against malware lists.
- Hot keys: a viral link can overload one cache shard. Replicate very hot keys or keep a small in-process cache.
- Multi-region: redirects served from every region (replicated reads); creates can go to a home region.
8. Practice drill
Without looking back:
- Why does the storage estimate matter more than QPS here?
- Walk through what happens on a redirect when Redis is down.
- How do you make sure two servers never hand out the same code?