Case Study: URL Shortener
Let's design a URL shortener like Bitly or TinyURL. This is a classic system design interview question that touches on every concept we've learned.
We'll follow the Architect's Framework: Clarify → Estimate → Design → Deep-Dive.
Step 1: Clarify Requirements
Functional Requirements
What does the system need to do?
- Given a long URL, generate a unique short URL
- Given a short URL, redirect to the original long URL
- Short URLs should expire after a configurable time (optional)
- Track analytics: click counts, referrers, etc. (optional)
Non-Functional Requirements
How well does the system need to perform?
Step 2: Back-of-Envelope Estimation
Traffic
URLs created per month: 100 million
URLs created per second: 100M / (30 * 24 * 3600) ≈ 40 writes/sec
Read:Write ratio: 100:1
Redirects per second: 40 * 100 = 4,000 reads/sec
Storage
URL length (average): 500 bytes
Short code: 7 bytes
Metadata: ~100 bytes
Total per URL: ~600 bytes
URLs stored for 5 years: 100M * 12 * 5 = 6 billion URLs
Storage needed: 6B * 600 bytes = 3.6 TB
Summary
| Metric | Value |
|---|---|
| Writes/sec | ~40 |
| Reads/sec | ~4,000 |
| Storage (5 years) | ~4 TB |
Step 3: High-Level Design
API Design
POST /api/shorten
Body: { "url": "https://example.com/very/long/url" }
Response: { "short_url": "https://short.ly/abc1234" }
GET /{short_code}
Response: 302 Redirect to original URL
Step 4: Deep Dive
Short Code Generation
How do we generate unique 7-character codes?
#### Option 1: Hash the URL
short_code = base62(md5(long_url))[:7]
Problem: Collisions. Different URLs might hash to same short code.
#### Option 2: Counter + Base62
# Global counter: 1, 2, 3, 4...
short_code = base62(counter)
Problem: Predictable URLs (security issue), counter becomes bottleneck.
#### Option 3: Pre-generated Keys (Recommended)
Generate keys offline, store in a key service. Each API server grabs a batch.
Base62 Encoding
Why 7 characters in Base62?
Base62 characters: a-z (26) + A-Z (26) + 0-9 (10) = 62
7 characters: 62^7 = 3,521,614,606,208 (3.5 trillion)
At 100M URLs/month = 1.2B/year
Time to exhaust: 3.5T / 1.2B = 2,917 years
7 characters is plenty.
Database Schema
CREATE TABLE urls (
short_code VARCHAR(7) PRIMARY KEY,
long_url TEXT NOT NULL,
user_id UUID,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
click_count INT DEFAULT 0
);
-- Index for expiration cleanup
CREATE INDEX idx_expires ON urls(expires_at);
Choice: Key-value store (like DynamoDB, Redis) would work well here since we're doing simple key lookups.
Caching Strategy
Cache-aside pattern for reads:def redirect(short_code):
# 1. Check cache
long_url = cache.get(short_code)
if long_url:
return redirect_302(long_url) # Cache hit!
# 2. Cache miss - check database
long_url = db.get(short_code)
if not long_url:
return 404
# 3. Update cache
cache.set(short_code, long_url, ttl=3600)
return redirect_302(long_url)
Expected cache hit rate: 80%+ (popular URLs get clicked repeatedly)
With 80% hit rate:
- 4,000 reads/sec × 0.2 (misses) = 800 DB queries/sec
Analytics (Async)
Don't block redirects to record analytics. Use a message queue:
def redirect(short_code):
long_url = get_url(short_code) # From cache or DB
# Async analytics
queue.publish({
'short_code': short_code,
'timestamp': now(),
'referrer': request.headers.get('Referer'),
'user_agent': request.headers.get('User-Agent'),
'ip': request.ip
})
return redirect_302(long_url)
Workers consume from the queue and batch-write to an analytics database.
Complete Architecture
Design Decisions Summary
| Decision | Choice | Why |
|---|---|---|
| Short code generation | Pre-generated keys | No collisions, no bottleneck |
| Database | Key-value store | Simple lookups, easy to shard |
| Caching | Cache-aside with Redis | Read-heavy workload |
| Analytics | Async via queue | Don't block redirects |
| Scaling | Horizontal + sharding | Stateless API, shardable DB |
Key Takeaways
- Start with requirements - both functional and non-functional
- Do the math - back-of-envelope calculations guide design decisions
- Optimize for the common case - reads dominate, so cache aggressively
- Decouple non-critical paths - analytics shouldn't slow down redirects
Next up: HLD Assessment - Test your knowledge across all topics.