Design a URL shortener
You paste a long link and get back something tiny like sho.rt/x7Kp2Q. Anyone who opens the tiny link lands on the original page. It is the classic first system design question: small enough for one interview, yet it still covers unique IDs, caching and staying online.
Legend 10 terms used below
- Short code
- The few characters at the end of a short link, like
x7Kp2Q. It is the key we use to find the original URL. - Redirect
- A reply from a server that says "what you want is over there" and gives the browser a new address to load instead.
- Availability
- How much of the time a system is up and answering. "Highly available" means it almost never goes down.
- Endpoint
- One address plus HTTP method that a server answers, like
POST /urls. Together, the endpoints make up the API. - Load balancer
- A machine that receives all incoming requests and spreads them across several app servers, so no single one gets overloaded.
- Primary key
- The column a table is organised by. The database keeps an index on it, so finding a row by its key is fast, even among billions.
- Base62
- Writing a number with 62 symbols (0-9, a-z, A-Z) instead of 10 digits, so big numbers turn into short strings.
- Cache
- A small, very fast store that keeps copies of popular data in memory, so most requests can skip the slower database.
- CDN
- Content delivery network: servers spread around the world that keep copies of answers close to users, so responses travel less distance.
- Replica
- A copy of the database that is kept up to date with the main one. If the main one dies, a replica can take over.
Functional requirements
- Submit a long URL and get a short one back.
- Opening the short URL takes you to the original site.
- Optional: pick a custom alias (
sho.rt/my-party) or an expiry date.
Out of scope: user accounts, click analytics, spam and malware filtering.
Non-functional requirements
- Unique short codes. One code must point to exactly one long URL, or people land on the wrong site.
- Fast redirects. A short link stands between a person and the page they want, so the extra hop should feel instant (a common target is under about 100 ms).
- High availability. If it goes down, every short link ever shared breaks at once. A brand-new link may take a moment to work everywhere; old links must keep working.
- Built for reads. A link is created once but may be opened thousands of times, so reads far outnumber writes.
Core entities
- Link: the short code, the original long URL, when it was made and (optionally) when it expires.
- User: who created the link. Only needed once we add accounts, which is out of scope today.
API design
One endpoint per functional requirement is enough.
/urlsCreate a short link. Alias and expiry are optional.
{
"long_url": "https://example.com/a/long/page",
"custom_alias": "my-party",
"expires_at": "2026-12-31"
}{
"short_url": "https://sho.rt/x7Kp2Q"
}/{short_code}Follow a short link. This is what a browser sends when someone clicks it.
GET /x7Kp2Q
Location: https://example.com/a/long/page 404 if the code is unknown 410 Gone if it has expired
High-level design
We build it one requirement at a time, simplest version first. I'll assume the usual setup: a few identical app servers that keep no data of their own, behind a load balancer.
1. Creating a short link
The client sends POST /urls. An app server checks the long URL looks valid, makes a new short code (first deep dive), saves the pair in the database and returns the short link.
| Column | Type | Note |
|---|---|---|
| short_code | text | primary key, e.g. x7Kp2Q |
| long_url | text | where the link goes |
| created_at | timestamp | when it was made |
| expires_at | timestamp, optional | empty means never |
Because short_code is the primary key, finding a link by its code stays fast even with billions of rows.
Do we need several databases? Say 100M new links a month, which is about 40 per second. At roughly 500 bytes a row, that is 50 GB a month, or about 600 GB a year. One database server handles both easily, so we don't split the data yet.
2. Following a short link
Someone opens sho.rt/x7Kp2Q, so their browser sends GET /x7Kp2Q. The app server looks up the code and replies with status 302 and a Location header holding the long URL. The browser follows it on its own. Unknown codes get 404, expired ones get 410.
Deep dives
How do we keep short codes unique?
We need a short string nobody else already has. Three common approaches:
I'd pick the counter, because uniqueness comes from the design itself, with no retry logic. Is 7 characters enough? 627 is about 3.5 trillion codes. At 100M links a month we use about 1.2 billion a year, so we would last thousands of years.
The downside: codes come out in order, so neighbouring links are guessable. If that matters, scramble the number with a reversible step before encoding. One counter is no bottleneck at about 40 new numbers a second, and the unique primary key stays as a safety net if it ever repeats a number after a crash.
How do we keep redirects fast?
Every click is a read. If each link is opened about 100 times, our 40 new links a second become about 4,000 redirects a second on average, and several times that at busy moments.
When a link expires or changes, delete its cache entry too, and never let a cached entry outlive the link's own expiry date.
How do we stay available?
App servers are interchangeable, so losing one is no big deal. The scary failure is the database: if the only copy dies, every redirect fails.
Replicas lag a moment behind, so a brand-new link might be missing from one for a fraction of a second. That is the trade we accepted: availability first, perfectly fresh data second.
Final design
Putting the deep dives together gives the complete picture: a counter for unique codes, a cache for fast redirects, and a replica so one database failure doesn't break every link. One change from the first sketch: an API gateway, the single front door that sends each request to the right service, splits the work into a write service and a read service. Clicks vastly outnumber new links, so the read side can be run and scaled on its own.
What you could add next
Interview tips
- Why 302 instead of 301?
- A 302 keeps every click coming through us, so we can expire, change or count links. Browsers cache a 301 and stop asking.
- Two people shorten the same URL. Same code?
- Not necessarily. A new code each time is simpler; de-duplicate only if asked (hashing gives it for free).
- How do custom aliases work?
- Check the alias is free (the unique primary key enforces it), and keep aliases in their own space, like
sho.rt/c/my-party, so a generated code can never land on one.
Common mistakes
- Jumping to sharding, queues and microservices before the basic create and redirect flow works.
- Ignoring the read/write imbalance. Reads outnumber writes by a lot, so the cache is where design effort pays off.
Sources: Hello Interview, Design a URL shortener like Bit.ly and its delivery framework; Alex Xu, System Design Interview (Vol. 1), URL shortener chapter; MDN, 302 Found.