System Design · Design Question
Design a URL Shortener
TinyURL-style service: create short links, redirect fast, handle collisions, and scale reads with cache + DB.
1. Clarify requirements
Functional
- Given a long URL, return a short URL (custom alias optional).
- GET short URL → 301/302 redirect to long URL.
- Optional: expiry, basic analytics (click count).
Non-functional
- Low latency redirects (p99 under a few ms at edge/cache).
- High read:write ratio (redirects dominate).
- Short codes hard to guess; no collisions served wrongly.
Back-of-envelope (example)
2. API
3. Short code design
Alphabet: [a-zA-Z0-9] = 62 chars. Length 7 → 62^7 ≈ 3.5e12
possibilities — enough for years at 100M/month.
| Approach | How | Trade-off |
|---|---|---|
| Hash + truncate | hash(longUrl), take 7 chars, retry on collision | Simple; need collision handling |
| Base62(counter) | Distributed ID → encode | No collision; IDs predictable unless salted |
| Random 7 chars | Generate, insert if free | Simple; retries under load |
Interview favorite: pre-generate or Snowflake-like ID, base62 encode. For custom aliases: unique constraint; reject if taken.
4. Data model
5. High-level design
6. Deep dives
Redirect path
Hottest path. Cache code→URL in Redis with TTL. Use 301 if you want browsers/CDNs to cache the redirect; 302 if every click must hit you for analytics. Many systems use 302 + async click event to a queue.
Collisions
Analytics
Do not increment click_count synchronously on the redirect hot path at
huge scale. Emit click(code, ts, meta) to Kafka/SQS; aggregate
asynchronously.
7. Scaling
- Stateless URL services behind LB.
- Redis cluster for redirect cache; DB read replicas if cache miss storm.
- Shard DB by code hash when single primary cannot hold data/writes.
- CDN optional in front for 301 caching of popular codes.
8. Edge cases
- Expired codes → 410 Gone; purge cache.
- Malicious long URLs → abuse detection, malware blocklists.
- Hot codes (viral) → cache + CDN; protect DB.
- Same long URL shortened twice → return existing code or create new (product choice).
- Custom alias races → unique constraint; loser gets 409.
Quick revision
- Clarify write QPS vs redirect QPS; redirects dominate.
- 7-char base62 is usually enough; handle collisions explicitly.
- API: POST create, GET redirect, optional stats/delete.
- Cache code→long_url on the hot path; async analytics.
- 301 vs 302 depends on caching vs counting every click.
- Scale: LB + Redis + shard DB by code when needed.
- Custom aliases = unique constraint + reject on conflict.