Why a URL shortener is a classic design question, and what it teaches
A URL shortener turns a long link into a short one such as sho.rt/2B and redirects visitors who open it. It is small enough to design in an interview and covers the usual ideas: unique IDs, a read-heavy workload, caching, storage and scale.
It gives you two things interviewers always ask about:
- Scalability: reads (redirects) outnumber writes (new links) by a large factor, so the design is about serving reads cheaply.
- Availability: a dead redirect breaks every page that links to it, so redirects must keep working when a part fails.
Requirements to confirm first:
- Functional: create a short link for a long URL, redirect from the short link, optionally a custom alias, expiry and click counts.
- Non-functional: very low redirect latency, high availability, short codes that cannot be guessed easily or that never collide.
A rough estimate (illustrative numbers, not measurements):
| Quantity | Assumption or calculation | Result |
|---|---|---|
| New links per day | Assumed | 100 million |
| Writes per second | 100,000,000 / 86,400 seconds | about 1,160 |
| Reads per second | 100 reads per write, so 100 x 1,157 | about 116,000 |
| Links per year | 100 million x 365 | 36.5 billion |
| Storage per year | 36.5 billion x 500 bytes (assumed row size) | about 18 TB |
| Codes needed in 5 years | 36.5 billion x 5 | 182.5 billion, well under the 3.5 trillion that 7 base62 characters give |
| Cache size | 20 percent of one day's new links: 20 million x 500 bytes | about 10 GB |
Writes are modest, so most of the design effort goes into serving about 116,000 reads per second.
Skip the design, or simplify it, when:
- You only need a few links: a table and one server are enough.
- Links never need to be unique across many writers: a single database sequence is simplest.
Try it
Shorten five links, then read them back. Switch how the short code is made, then break something.
URL Shortener
Step 1 of 11 · Press Space to play, arrow keys to stepInitial state. Writes create short codes, reads redirect them. The database is the source of truth, the cache holds hot links.
- tick:
- 0
- collisions:
- 0
- cache hits:
- 0
- cache misses:
- 0
- failed reads:
- 0
1 tick = one step of the simulation.
Things to try:
- Run Counter + base62 and watch each code come from the next number, so no two links share a code.
- Switch to Hash of the URL. The code space here has only 4 values to make a collision visible, which is two different URLs getting the same code. In this simulation a colliding URL is simply skipped, so only two of the five links are saved; a real service would retry with a salt until it finds a free code.
- Choose Database is down for reads. The cache is filled only by reads, and none has happened yet, so all four redirects fail (4 failed reads, 0 hits). A cache protects only links that were read before the outage. The summary caption describes a system that has warmed up; it does not happen in this short run.
- Then try Cache is empty. Every read goes to the database, repeats included (0 hits, 4 misses), because the cache is held empty for the whole run. Compare None, where the second and third reads of the first link hit the cache (2 hits, 2 misses).
Ways to create the short code
| Approach | How it works | Good when | Weakness |
|---|---|---|---|
| Counter + base62 | A global counter gives the next number, encoded in letters and digits | You want unique codes with no collisions | The counter is one more component, and sequential codes are guessable |
| Hash of the URL | Hash the long URL and keep the first characters | The same URL should give the same code | Truncation causes collisions, which need a retry |
| Random code | Pick random characters and check that the code is unused | Codes must not be guessable | Needs a check against the database on every write |
| Pre-generated keys | A separate service makes unused keys in advance and hands them out | Very high write rate | Another service to run, and keys must be marked used |
Seven base62 characters give about 3.5 trillion codes (62 to the power 7), which is plenty for most services.
Redirect type: 301 vs 302
- 301 (permanent): the browser caches the redirect, so later visits skip your servers. That cuts load but you stop seeing clicks, and you cannot change the target.
- 302 (temporary): every visit comes to you. You can count clicks and change or expire the link, at the cost of more traffic.
How the read path works
Most of the work is a redirect, which is a key lookup:
- The request
GET /2Breaches a stateless web server through a load balancer. - The server looks in the cache for the code. A hit returns the long URL straight away.
- On a miss it reads the database by primary key, stores the result in the cache with an expiry, and redirects.
- A click event goes to a queue, so counting does not slow the redirect.
Because links are read far more than they are written, the cache can hold only the hot ones and still absorb most traffic.
URL shortener vs the alternatives
| Concern | Purpose-built shortener | Long URLs in pages | A single table, no cache |
|---|---|---|---|
| Short, shareable links | Yes | No | Yes |
| Change the target later | Yes | No | Yes |
| Handles heavy read traffic | Yes, with a cache | Not applicable | Only at small scale |
| Click analytics | Yes | No | Yes, but slow |
| Extra moving parts | Cache, ID service, queue | None | Few |
| Best for | Public links at scale | Internal use | A prototype |
Build the cache and ID service when traffic grows. A single table is enough for a prototype.
Avoiding a new single point of failure
- ID counter: give each web server its own range of numbers ahead of time, so a counter outage does not stop writes.
- Cache: run a replicated cache and let the service fall back to the database if it is down.
- Database: use replicas for reads and a standby for failover. Partition by code when one machine is too small.
You accept eventual consistency on replicas: a link created a moment ago may take a short while to be readable everywhere.
More failure cases to mention:
- Read after write: the creator often opens the new link at once. If a replica has not received it yet, the visitor sees a 404, so retry a miss against the primary before giving up, and do not cache a 404 for long.
- Hot key: one very popular link can overload a single cache node. Keep a short-lived copy in each web server's memory, or replicate that key.
- Cache stampede: when a popular entry expires, many requests miss at once. Let one request refill the cache and make the others wait.
- Cache expiry and link expiry: a cache entry must never outlive the link's own expiry time, or an expired link keeps redirecting.
Common mistakes
- Using the hash alone. Collisions happen. Detect them and retry.
- Sequential codes for private links. They can be guessed. Use random or shuffled codes if links are secret.
- Counting clicks in the redirect path. Send events to a queue instead.
- No expiry or cleanup. Dead links fill the database for ever.
- Not validating the target. Block unsafe or looping URLs.
- One huge counter row. It becomes a hotspot. Hand out ranges.
- Partitioning by code range. Counter codes only increase, so every new write lands on the last partition. Partition by a hash of the code instead.
How to talk about a URL shortener in an interview
- Start with requirements and a rough estimate: new links per day, a read to write ratio of about 100 to 1, and storage per link. With the illustrative numbers above that is about 1,160 writes and 116,000 reads per second, and about 18 TB a year.
- Choose the code scheme (counter plus base62 or random) and say why.
- Draw load balancer, stateless servers, cache and database, and say the read path out loud.
- Mention 301 vs 302, a queue for analytics, expiry and abuse checks. Name the trade-off: a cache makes reads fast but can serve a link that was just removed until it expires.