System Design · Note 13 of 22
Design a URL Shortener
Quick answer
How do you design a URL shortener?
Generate a unique ID for each long URL, encode it in base62 to get a short code of six or seven characters, and store code and URL in a key-value store. On a read, look the code up in a cache, falling back to the database, and answer with an HTTP 301 or 302 redirect. Reads far outnumber writes, so caching and replication carry the load.
On this page
The classic first system design question: small to state, and it touches estimation, ID generation, storage, caching and HTTP.
Requirements
- Functional: shorten a long URL; redirect a short code to it; optional custom alias and expiry.
- Non-functional: redirects fast and highly available; codes unique and not easily guessed (if required).
- Read-heavy: a link is created once and opened many times.
Capacity estimate
- State the assumptions, then derive QPS, storage and the code length.
- The figures below follow from these assumptions; change them and the numbers move.
Start from stated assumptions, then derive the numbers the design must meet. Say the assumptions out loud; an interviewer may change them.
Transcript · 7 steps
API
POST /api/shorten {"url": "https://shop.example/item/48213"} → 201 {"code": "2bYsMq"}
GET /2bYsMq → 302 Location: https://shop.example/item/48213Write path
- Counter + base62: a unique number per URL, encoded in 62 characters.
- No collisions by construction; the ID source must be shared or partitioned.
- Hand each server a range of IDs so it does not ask on every write.
POST /shorten carries a long URL. The API server asks a counter for the next unique id, encodes it in base62 and stores the pair.
Transcript · 5 steps
To encode id 2,009,215,674, divide by 62 again and again. Each remainder, 0–61, picks one character from 0–9, a–z, A–Z.
Transcript · 8 steps
Counter vs hash
| Counter + base62 | Hash + truncate | |
|---|---|---|
| Collisions | None | Possible — check and retry |
| Coordination | Shared ID source | None |
| Same URL twice | New code each time | Same code (dedupes) |
| Guessable | Sequential unless shuffled | No |
Read path
- Look the code up in a cache (Redis, memcached) first; database on a miss.
- Popular links stay hot, so most reads never reach the database.
- Unknown code →
404; expired →410 Gone.
A browser opens short.example/<code>. The server looks the code up in an LRU cache of 2 entries first and goes to the database only on a miss, then redirects.
Transcript · 8 steps
301 vs 302
- 301 Moved Permanently: browsers cache it; fewer requests reach you, but you lose click counts and cannot change the target.
- 302 Found: every click comes back; analytics, edits and expiry work.
- Choose 302 when analytics matter, 301 when load matters most.
Scaling it
- Database: key-value store sharded by code; replicas for reads.
- Stateless app servers behind a load balancer.
- Rate limiting on
POST /shortenagainst spam. - Analytics off the hot path: write click events to a queue.
Interview questions
- How many characters does the code need? Estimate total rows, then the smallest n with 62^n above it.
- How do you avoid two servers issuing the same ID? Pre-allocated ID ranges per server, or Snowflake-style IDs.
- Where is the bottleneck? Reads; a cache in front of a sharded key-value store absorbs them.
- 301 or 302? 302 if you need click analytics or editable links; 301 to shed load.
Next: Back-of-the-Envelope Estimation · Before this: Microservices and API Gateway
Common questions
How long should a short code be?
Long enough for every URL you will ever store. With 62 characters, 6 characters give about 56.8 billion codes and 7 give about 3.5 trillion. Estimate the total rows over the system's life and pick the smallest length whose 62^n covers it, with room to spare.
Should I use a counter or a hash for the short code?
A counter encoded in base62 never collides and gives the shortest codes, but needs a distributed ID source and makes codes guessable in order. Hashing the URL and taking the first characters needs no coordination, but truncated hashes collide, so every write must check and retry.
Should a URL shortener use 301 or 302?
A 301 is permanent, so browsers and caches remember it and later clicks never reach your servers — less load but no click counts. A 302 is temporary, so every click comes back, which costs more traffic but lets you count clicks, change the target or expire the link.
Which database suits a URL shortener?
The data is one large table read by its key with no joins, so a key-value or wide-column store such as DynamoDB or Cassandra fits and shards easily. A relational database with an index on the code also works at moderate scale.
How do you generate unique IDs across many servers?
Give each server a range of IDs from a coordinator such as ZooKeeper or a database sequence, so it hands them out locally until the range runs out. Snowflake-style IDs, made of a timestamp, a machine number and a sequence, are another common choice.