Module 07 // Case studiesLesson 26 / 3425 min read
Design a URL shortener
A URL shortener turns a long link into a short code and redirects anyone who visits the code. It's read-heavy, needs unique short codes, and must redirect in milliseconds.
Think of it like this
Analogy
A coat-check desk at a big event. You hand over a long, bulky coat (the long URL) and get back a tiny numbered ticket (the short code). Anyone with the ticket can get the coat. The desk must never hand out the same ticket number twice, and it should find coats quickly, because the line for picking them up is much longer than the line for dropping them off.
The problem it solves
Long URLs are awkward to share in messages, on printed material and in social posts. A shortener gives you something like sho.rt/a8Kz2Qp. The hard parts aren't the idea, they're the numbers: generating billions of unique codes across many servers, and serving redirects fast for a link that suddenly goes viral.
A real example
Example
Short links in a marketing text message
A shop sends a sale text to 2 million customers with a link like sho.rt/x7K2a. Within ten minutes, hundreds of thousands of people tap it.
Each tap is a lookup from code to long URL. The answers sit in a cache, so most redirects never touch the database and finish in a few milliseconds.
Every click also needs counting for the marketing report. Instead of updating a database row on every tap, the redirect service sends a click event to a queue, and a worker adds up the counts in batches. The link expires 30 days after the sale, and the code can be reused later.
How it works
1. Requirements
Functional: create a short link for a long URL (optionally with a custom alias and an expiry); visiting the short link redirects to the long URL; basic click analytics.
Non-functional: redirects are fast (p99 under about 50 ms server-side) and highly available; codes are unique and not trivially guessable; durable (a link should work for years).
2. Estimates
Assume 100 million new links a month and 100 reads for every write.
- Writes: 100 M ÷ (30 × 86,400 s) ≈ 40 per second; reads ≈ 4,000 per second on average, maybe 10–20 thousand at peak.
- Storage: about 500 bytes per link × 1.2 billion a year ≈ 600 GB a year, a few TB over five years with replicas.
- This is read-heavy and small. Caching will handle most reads.
3. API
POST /api/links {"url": "https://example.com/very/long", "alias": "spring-sale", "expiresAt": null}
→ 201 {"code": "a8Kz2Qp", "shortUrl": "https://sho.rt/a8Kz2Qp"}
GET /a8Kz2Qp
→ 302 Location: https://example.com/very/longCreating accepts an idempotency key so client retries don't create duplicate links.
4. Generating short codes
Base62 (0-9a-zA-Z) with 7 characters gives about 3.5 trillion codes.
| Approach | How | Good | Bad |
|---|---|---|---|
| Hash the URL | First 7 chars of a hash of the URL | Same URL → same code | Collisions need handling; can't have two codes for one URL |
| Random code | 7 random base62 chars, insert if not taken | Unguessable, simple | Needs a uniqueness check per insert |
| Counter ranges | Each server grabs a block of IDs (say 1,000) from a coordinator, then encodes each ID | No collisions, no per-insert checks | Sequential codes are guessable unless scrambled |
A solid choice: counter ranges per server, with each number scrambled by a reversible permutation before base62 encoding. That's unique without coordination per request, and not enumerable. Custom aliases are checked with a conditional insert.
5. Data model
A key-value store fits perfectly: code → {longUrl, createdAt, expiresAt, ownerId}. Use the code as the partition key (shard key); it's evenly distributed and every read is a single lookup.
6. High-level design
Reference architectureURL shortener
- Users to Route 53: lookup
- Users to CloudFront
- CloudFront to Application Load Balancer: miss
- Application Load Balancer to ECS on Fargate
- ECS on Fargate to ElastiCache for Valkey
- ECS on Fargate to DynamoDB
- ECS on Fargate to Kinesis Data Streams
- Kinesis Data Streams to Amazon S3
On a redirect: the CDN may answer directly if the redirect is cacheable. Otherwise the app checks the cache (hot codes are tiny and fit easily in memory), falls back to the database on a miss, and returns a redirect. Each click event goes to a stream, asynchronously, so analytics never slows the redirect.
7. Deep dives
- 301 vs 302: 301 lets browsers and CDNs cache the redirect, which saves load but hides repeat clicks. Use 302 if analytics or editable destinations matter.
- Viral links: cache at the edge with a short TTL, plus the in-memory cache. Request coalescing stops a stampede on a miss.
- Abuse: shorteners attract spam and phishing. Check URLs against safe-browsing lists on creation, rate-limit creation per user and IP, and support takedowns.
- Expiry: a TTL on the record, plus a background job to clean up.
Trade-offs
What you gain
- Very simple data model and access pattern; scales by sharding on the code.
- Read-heavy, so caching makes it cheap and fast.
- Analytics are decoupled through a stream.
What it costs you
- Cached 301s make click counts approximate.
- Code generation needs care to be unique, fast and unguessable.
- A public service attracts abuse that needs constant work.
When not to use it
- For internal links between your own pages, you don't need a separate service; use your own routing.
- Don't add a relational database with joins here unless you need rich ownership features; the core is pure key-value.
In the cloud
NoSQL databasesEvery major cloud sells this as a managed service, so you configure it rather than build it.
AWS3
- Amazon DynamoDB (opens vendor docs)
Serverless key-value and document database with single-digit-millisecond reads at any scale.
- Amazon DocumentDB (opens vendor docs)
MongoDB-compatible document database.
- Amazon Neptune (opens vendor docs)
Graph database for highly connected data like social graphs and fraud rings.
Azure1
- Azure Cosmos DB (opens vendor docs)
Globally distributed NoSQL database with document, key-value, graph and column-family APIs.
Google Cloud2
- Firestore (opens vendor docs)
Serverless document database with real-time sync for apps.
- Bigtable (opens vendor docs)
Wide-column database for huge, fast-growing datasets like time series and events.
Interview questions
Try answering out loud first, then open the model answer.
How do you guarantee short codes are unique across many servers?
Model answer
Either coordinate in batches or check on insert. Batched: a small coordinator (a database sequence or a consensus store like etcd) hands each app server a range of IDs; the server uses them locally, so uniqueness is guaranteed without a per-request call. To avoid guessable codes, apply a reversible scramble before base62 encoding. Check-on-insert: generate a random code and insert with a condition that it doesn't exist, retrying on the rare collision. Both work; ranges have no hot path to a shared component.
How would you handle 1 million redirects per second for a viral link?
Model answer
Let the edge absorb it. Serve the redirect from the CDN with a short cache TTL (or a 301 if analytics can be approximate), so nearly all requests never reach the origin. Behind it, keep the code in an in-process cache on every app server and in the shared cache, with request coalescing on misses. Make click logging asynchronous and batched. Auto-scale the stateless app tier. The database sees almost no load for that link.
How would you add click analytics without slowing redirects?
Model answer
The redirect handler publishes a small click event (code, timestamp, referrer, coarse location from the IP, user agent) to a stream and returns immediately, without waiting. Consumers aggregate counts per code per minute into a time-series or analytics store and archive raw events to object storage. Dashboards read the aggregates. If the stream is briefly unavailable, the app drops or buffers events rather than failing redirects.
Quiz
Pick an answer to see whether it’s right and why.
0 of 6 answered
Key terms
- Base62
- Writing numbers with 0–9, a–z and A–Z, so short strings cover huge ranges (7 characters ≈ 3.5 trillion values).
- Key-value store
- A database that stores values under unique keys, like a giant dictionary. Very fast lookups by key.
- Cache
- A small, fast store that keeps copies of data that is expensive to fetch, so repeat requests are quicker.
- CDN
- Content delivery network. Servers around the world that keep copies of files close to users.
- Shard key
- The field used to decide which shard a record lives on, such as user ID.
- Idempotency
- Doing the same operation twice has the same effect as doing it once, which makes retries safe.
Progress is saved in this browser only.