Stage 4 · Provision
System Design Interviews
Design a URL Shortener
Base62 keys, collision handling, redirects, analytics, and abuse prevention.
Requirements
A URL shortener takes a long URL and returns a short URL. When users visit the short URL, they are redirected to the original URL. Key requirements: generate short keys, handle redirects with low latency, track click analytics, and prevent abuse.
Given: https://example.com/very/long/url?with=params&and=more
Return: https://sho.rt/abc123
Visit https://sho.rt/abc123
→ 301/302 redirect to https://example.com/very/long/url?with=params&and=more
Non-functional:
- Redirect latency: < 50ms
- Availability: 99.99%
- 100M URLs created per day
- 10B redirects per dayThe system must handle 10B redirects per day (~115K QPS) with sub-50ms latency. Read-heavy workload (100:1 read-to-write ratio).
Key Generation
The short key must be unique, short, and URL-safe. Base62 encoding (a-z, A-Z, 0-9) provides 62 possible characters per position. A 7-character Base62 key provides 62^7 = 3.5 trillion unique URLs.
import string
import random
CHARSET = string.ascii_letters + string.digits # a-z, A-Z, 0-9
def generate_key(url: str) -> str:
# Option 1: Random key (simple, risk of collision)
return ''.join(random.choices(CHARSET, k=7))
def generate_key_deterministic(url: str) -> str:
# Option 2: Hash-based (deterministic, check for collision)
import hashlib
hash_val = int(hashlib.md5(url.encode()).hexdigest(), 16)
key = ""
while hash_val > 0:
key += CHARSET[hash_val % 62]
hash_val //= 62
return key[:7]
def generate_key_autoincrement() -> str:
# Option 3: Auto-increment + Base62 (guaranteed unique)
counter = redis.incr("url_counter")
key = ""
while counter > 0:
key = CHARSET[counter % 62] + key
counter //= 62
return keyAuto-increment + Base62 is the simplest approach for guaranteed uniqueness. Use a distributed counter in Redis for high-throughput environments.
Data Model
CREATE TABLE urls (
id BIGSERIAL PRIMARY KEY,
short_key VARCHAR(7) UNIQUE NOT NULL,
original_url TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
user_id BIGINT,
click_count BIGINT DEFAULT 0
);
CREATE INDEX idx_urls_short_key ON urls(short_key);
CREATE INDEX idx_urls_user_id ON urls(user_id);
CREATE INDEX idx_urls_created_at ON urls(created_at);The short key is the primary lookup path. The index on short_key enables O(1) lookups. The user_id index enables listing all URLs for a user.
Architecture
Client ──► API Gateway ──► URL Service
│
├── Write path:
│ Generate key → Store in DB → Cache in Redis
│
└── Read path:
Lookup key → Cache hit? Serve
→ Cache miss? DB lookup → Cache → ServeThe read path is optimized for speed: Redis cache serves most requests. The write path generates a unique key and stores the mapping.
Analytics & Click Tracking
Click analytics should not slow down redirects. Use asynchronous event logging: after serving the redirect, emit a click event to Kafka. A separate analytics service processes these events for dashboards and reporting.
Use 301 (permanent) for SEO benefit — search engines transfer link equity. Use 302 (temporary) if you need to track clicks or if the redirect may change. Most URL shorteners use 301 to reduce server load (browsers cache 301).
Abuse Prevention
- Rate limiting — Limit URL creation per user/IP.
- URL validation — Check against malware/phishing databases.
- Link expiration — Auto-expire short URLs after configurable period.
- CAPTCHA — Require CAPTCHA for bulk URL creation.
- Blocklist — Maintain a blocklist of known malicious URLs.
Implement redirects at the CDN edge (Cloudflare Workers, Lambda@Edge). This eliminates the need to origin for redirects, reducing latency and origin load to near zero.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.