Case Study: Designing a URL Shortener
A full worked example — requirements, capacity estimation, data model, and architecture.
This case study ties together every concept from this track — scale estimation, caching, database design, and CAP trade-offs — into one worked design, the way a real system design interview expects.
1. Clarify requirements
Functional:
- Given a long URL, generate a short URL (e.g.,
noa.link/aZ9kLQ). - Visiting the short URL redirects (HTTP 301/302) to the original long URL.
- Optionally: custom aliases, expiration dates, click analytics.
Non-functional:
- Extremely read-heavy (every redirect is a read; shortening is a write) — assume a 100:1 read:write ratio.
- Redirects must be very low latency (users notice slow redirects immediately).
- High availability — a shortener that's down breaks every link that uses it, everywhere.
- Uniqueness — no two long URLs should silently collide on the same short code (or if they can, it must be handled deliberately).
2. Estimate scale
Assume: 100M new short URLs created per month, 100:1 read:write ratio
Writes/sec: 100,000,000 / (30 * 24 * 3600) ≈ 39 writes/sec
Reads/sec: 39 * 100 ≈ 3,900 reads/sec
Storage (assume 5-year retention):
100M/month * 12 * 5 = 6,000,000,000 records
~500 bytes/record (URL + metadata) ≈ 3 TB total
Conclusion: this is a read-dominated system at moderate-but-real scale — caching in front of the database is clearly justified, and the database choice matters less than getting the read path fast.
3. API design
POST /api/shorten
body: { "url": "https://example.com/a/very/long/path" }
response: { "short_url": "https://noa.link/aZ9kLQ" }
GET /{short_code}
response: HTTP 301 redirect to the original long URL
4. How to generate the short code
Option A — Base62 encode an auto-incrementing ID. A database (or distributed ID generator) hands out sequential integer IDs; encode each in base62 (a-z, A-Z, 0-9) to get a short, URL-safe string.
id = 125_100_524
base62(125100524) = "8xwZ2"
Simple and guarantees uniqueness by construction (no collision checking needed), but a centralized ID generator can become a bottleneck/single point of failure at very high write volume — mitigated with pre-allocated ID ranges per server, or a distributed ID scheme (e.g., Twitter's Snowflake).
Option B — Hash the long URL (e.g., MD5/SHA-256) and take the first 6–8 characters. Simpler infrastructure (no central counter), but requires a uniqueness check against existing codes and a collision-handling strategy (append a character and rehash, retry).
For this design, Option A is the better trade-off: no collision-handling complexity, and ID generation scales fine with pre-allocated ranges.
5. Data model
CREATE TABLE urls (
id BIGINT PRIMARY KEY, -- source for the base62 short code
long_url TEXT NOT NULL,
short_code VARCHAR(10) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NULL
);
CREATE INDEX idx_urls_short_code ON urls (short_code);
A relational database is a perfectly reasonable choice here — the data model is simple, and a key-value store (DynamoDB, Cassandra) would also work well if global multi-region write scale became a requirement later.
6. Where caching fits
Given the 100:1 read:write ratio, a cache in front of the database is one of the highest-leverage decisions in this whole design:
GET /{short_code}
1. check Redis for key "url:{short_code}"
2. hit -> redirect immediately (sub-millisecond)
3. miss -> query the database, populate Redis with a TTL, then redirect
Popular short URLs (viral links) will have an extremely high cache hit rate, taking the vast majority of read traffic off the database entirely — exactly the cache-aside pattern from the caching lesson in this track.
7. Scaling the redirect path
- CDN / edge caching: since a redirect response for a given short code rarely changes, it can even be cached at the CDN edge for anonymous traffic, serving redirects with no origin round-trip at all for hot links.
- Read replicas: for cache misses, read replicas absorb read load without touching the primary, which stays focused on writes (new short URL creation).
- Horizontal scaling of the API layer: the redirect service itself is stateless — it can scale horizontally behind a load balancer with no coordination needed between instances.
8. CAP trade-off for this system
A short URL that's briefly stale (e.g., redirecting to a version of the mapping that's a few seconds out of date right after creation) is essentially harmless — nobody notices. This system should clearly favor availability over strict consistency (AP-leaning): if a cache or replica is slightly behind, that's a completely acceptable trade for keeping redirects fast and always available, per the CAP/PACELC discussion earlier in this track.
9. Summary architecture
Client --> CDN (edge cache) --> Load Balancer --> API servers (stateless)
|
cache-aside | writes
+-------------+------------+
v v
Redis Primary DB
(short_code -> URL) (+ read replicas)
Common mistakes (in this specific design, and in interviews generally)
- Reaching for a hash-based short code without a real collision-handling plan.
- Forgetting to estimate scale first — the entire justification for caching and read replicas comes directly from the 100:1 read-heavy ratio.
- Over-engineering the very first version with sharding before establishing that a single primary + replicas + cache can't keep up — at ~39 writes/sec, a single primary handles writes easily.
Interview questions
Q: Why is a Base62-encoded auto-incrementing ID usually preferred over hashing the URL? It guarantees uniqueness by construction with no collision-checking or retry logic needed, and produces predictably short codes. Hashing requires handling the (rare but real) possibility of two different URLs producing colliding short hashes.
Q: Where would you put a cache in this system, and why? In front of the database, keyed by short code, using the cache-aside pattern — because the read:write ratio is extremely lopsided (100:1), caching the hot redirect path removes the vast majority of load from the database with a small, well-understood component.
Q: Should this system favor consistency or availability, and why? Availability — a short URL redirect being briefly stale (serving a cached mapping a few seconds old) causes no real harm, while the system being unavailable breaks every link using it, everywhere, immediately. This maps directly to an AP-leaning design per the CAP theorem.