Design a URL Shortener: Estimates, ID Generation, Redirects and Scale

The URL shortener is the "hello world" of system design interviews. Everyone knows the shape, which is exactly why interviewers use it: the difference between candidates shows up in ID generation, the redirect choice and the numbers, not in the boxes.

Requirements

Functional

  • Create a short link for a long URL, optionally with a custom alias.
  • Redirect a short link to the original URL.
  • Optional: expiry dates and basic click counts.

Non-functional

  • Redirects must be fast (they sit in front of every click) and highly available.
  • Short codes must be unique and should not be guessable in sequence if links can be private.
  • Reads hugely outnumber writes.

Out of scope unless asked: user accounts, detailed analytics dashboards, link previews.

Estimates

Assume 100 million new links per month and a 100:1 read/write ratio.

  • Writes: 100M ÷ (30 × 86,400 s) ≈ 40 per second.
  • Reads: ≈ 4,000 redirects per second on average, ~10,000 at peak.
  • Storage: 100M × 12 months × 5 years = 6 billion links. At ~500 bytes each (URL plus metadata) that's about 3 TB.

What it means: writes are tiny, the read path needs caching, and 3 TB fits a partitioned key-value store comfortably.

API

POST /links        { longUrl, customAlias?, expiresAt? }  -> { shortCode, shortUrl }
GET  /{shortCode}  -> 301 or 302 redirect to longUrl

Data model

One table keyed by the short code:

FieldNotes
short_code (key)7 characters, base62
long_urlUp to a few KB
created_at, expires_atFor expiry
owner_idOptional

The only hot query is "look up by short code", so a key-value store (or any database with a primary-key lookup) fits well.

Generating short codes

How long does the code need to be? With base62 (a–z, A–Z, 0–9):

  • 6 characters → 62⁶ ≈ 56.8 billion codes.
  • 7 characters → 62⁷ ≈ 3.5 trillion codes.

Seven characters leaves plenty of room for 6 billion links. Now, three ways to produce them:

ApproachHowProsCons
Hash and truncateHash the long URL (e.g. MD5), base62-encode, take 7 charsSame URL can map to the same codeCollisions must be detected and retried; truncation makes them more likely
Counter + base62A unique number per link, encoded in base62No collisions, simpleA single counter is a bottleneck; sequential codes are guessable
Random + checkGenerate 7 random base62 chars, insert only if absentUnguessable, no coordinationNeeds a uniqueness check (a conditional insert) on every write

A strong answer for scale: counter ranges. A small coordination service hands each app server a block of IDs (say 1,000,000 at a time); servers allocate from their block locally, with no per-request coordination. If guessability matters, shuffle the number with a reversible permutation before encoding, or use the random approach.

Custom aliases go through the same uniqueness check as random codes.

The redirect: 301 or 302?

  • 301 (permanent): browsers cache the redirect, so repeat clicks may never reach your servers. Less load, but you lose click counts and can't change the destination later.
  • 302 (temporary): every click comes back to you. More load, but accurate analytics and editable links.

Choose based on the requirements and say the trade-off. If analytics matter, 302.

High-level design

  1. Write path: client → API servers → ID allocation → write to the key-value store.
  2. Read path: client → load balancer → redirect service → cache → store on a miss → redirect response.

A small fraction of links gets most of the clicks, so a cache (Redis, or a CDN for the very hottest links) absorbs most redirects. Cache-aside with an LRU policy works well; link targets rarely change.

Scaling and follow-ups

  • Partitioning: shard the store by short code (hash-based), which spreads load evenly because codes are effectively random.
  • Click analytics: don't write a row synchronously on every redirect. Emit an event to a queue and aggregate asynchronously.
  • Expiry: check expires_at on read; clean up expired rows in a background job.
  • Abuse: rate limit creation per user or IP, and check new URLs against a malware/phishing list.
  • Availability: replicate the store across zones; the redirect path should keep working if the write path is down.

Common mistakes

  1. No estimates, so no justification for the cache or the storage choice.
  2. Hashing the URL without handling collisions.
  3. A single global counter with no plan for its throughput or failure.
  4. Never mentioning 301 versus 302.
  5. Writing analytics synchronously in the redirect path.

Practise it step by step. Prepd's URL shortener question walks through requirements, capacity estimates, the data model, the architecture and the API as separate steps, with written feedback on each answer.

Practise it