Skip to main content

Design a URL Shortener

Premium

You're asked to design a URL shortener: a service that takes a long URL, returns a short one, and redirects anyone who visits the short link to the original destination. It should support hundreds of millions of new links a month, optional custom aliases, and click analytics for link owners.

The write path is simple, but one link may be followed millions of times from around the world for years after it was created. Redirects outnumber creations by orders of magnitude, so let's design around the read path.

Clarify the requirements

  • How short is short? This sets the key space. Base62 uses the ten digits, 26 lowercase letters, and 26 uppercase letters, giving each character 62 possible values. A seven-character code therefore gives 6273.5×101262^7 \approx 3.5 \times 10^{12} possible links, which is comfortably enough. Calculate it before choosing a key generator.
  • Custom aliases? If users can request /my-company, you need a uniqueness check on a user-supplied string, which is a different write path from generated keys.
  • Do links expire? Expiry changes the storage story and adds a cleanup job. Many versions of this question skip it.
  • Are analytics in scope? Counting clicks adds a high-volume write path to an otherwise read-heavy service, so clarify this before designing the redirect path.

Assume for this breakdown: generated keys, optional custom aliases, no expiry, and click analytics in scope.

Back-of-envelope numbers

  • Writes: 100M links/month40 writes/sec100\text{M links/month} \approx 40 \text{ writes/sec}
  • Reads at a 100:1 ratio: 40×100=4,000 redirects/sec40 \times 100 = 4{,}000 \text{ redirects/sec}, with peaks several times higher
  • Storage over five years: 100M/month×60 months×500 B3 TB100\text{M/month} \times 60 \text{ months} \times 500\text{ B} \approx 3\text{ TB}

The dataset is large but manageable as a key-value workload. Reads outnumber writes by roughly two orders of magnitude, so we'll spend most of our effort on redirect latency and caching.

High-level architecture

POST /shorten

GET /abc123

miss

click event

Client

① API service

② Key generator

③ Cache

④ Link store

⑤ Analytics queue

Components
  1. API service. Stateless, handles both creation and redirect lookups.
  2. Key generator. Produces a unique short code, discussed below.
  3. Cache. Holds hot mappings. Because entries are immutable, this cache is unusually easy to reason about.
  4. Link store. The durable mapping from short code to long URL, plus owner and creation time.
  5. Analytics queue. Click events go here, never into the redirect's critical path.
Two paths through one mapping. Writes generate a key and store it; reads hit a cache first and fall through to the store only on a miss.

The schema is small enough to write out:

Link short_code PK long_url user_id FK, index created_at

Deep dive 1: generating the key

Let's compare four ways to generate a short code:

  • Hash the URL and truncate. Hash the long URL with MD5 or SHA, encode the result in base62, and take the first 7 characters. Truncation creates collisions, so you need a collision check and a retry with a salt. It also means the same URL always maps to the same code, which may or may not be what you want.
  • Random generation with a uniqueness check. Generate 7 random base62 characters, attempt an insert, retry on conflict. With 3.5 trillion possible keys and 6 billion used, collisions are rare enough that retries are almost free. A unique constraint on short_code makes correctness independent of luck.
  • A counter, encoded in base62. Convert each sequential integer into a shorter string using the base62 alphabet. This guarantees uniqueness without collision handling. The catch is that a global counter is a distributed systems problem in itself, and sequential codes leak how many links exist and let anyone enumerate other people's links.
  • Pre-generated key ranges. A coordination service hands each API instance a block of a million unused keys. Instances then allocate locally with no coordination per request, which removes the counter bottleneck while keeping uniqueness.

Deep dive 2: making redirects fast

A redirect performs one lookup and returns a 301 or 302. Because a short link's mapping never changes, we can cache it without an invalidation path.

  • Cache aggressively. Link popularity is extremely skewed. A small fraction of links carry most traffic, so a modest cache can absorb a high share of reads. Use LRU eviction and let cold links fall through.
  • Choose between 301 and 302 deliberately. A 301 (permanent) lets browsers and intermediaries cache the redirect, which cuts traffic but hides later clicks from analytics. A 302 (temporary) sends every click through the service. If analytics matter, use 302 and explain the added load.
  • Go global. Redirect latency is dominated by network round trips, so replicate read-only copies of the mapping close to users, or serve the redirect from an edge layer entirely.

Deep dive 3: analytics without slowing the redirect

Recording a click must never block sending the user to their destination. Fire the click event onto a queue and return the redirect immediately (event-driven and pub/sub). Consumers then aggregate into counters and time series, which is a small batch pipeline.

At 4,000 redirects per second, exact per-click rows get expensive quickly. If we only need hourly counts, consumers can roll up events by link and hour. HyperLogLog can estimate unique visitors with even less storage.

  • Malicious links. A shortener is an attractive way to disguise a phishing URL. Check destinations against a safe-browsing list at creation time and re-check periodically, since a benign URL can turn malicious later.
  • Hot keys. One viral link concentrates traffic on a single cache entry. Replicate that entry across cache nodes or add a small in-process cache in the API tier.
  • Enumeration. Sequential codes let anyone walk your entire link set. Random or range-allocated keys avoid this.

Common pitfalls

  • Designing the write path in detail and rushing the read path. The reads are 100× the writes; spend your time accordingly.
  • A single global counter with no discussion of its bottleneck. It works, but you have to name the coordination cost.
  • Ignoring the 301/302 trade-off. It's the one place where a caching decision directly deletes a product feature.
  • Putting analytics writes in the redirect path. Every click now waits on your analytics store.

Leveling signals

Mid-levelDesigns the API and schema, picks a key generation scheme, and adds a cache in front of the store. Does the base62 key space math.
SeniorRemoves coordination from key generation with pre-allocated ranges, defends 301 versus 302 against the analytics requirement, and moves click recording off the redirect path.
Staff+Treats redirect latency as a global problem and pushes reads to the edge. Sizes the cache against link popularity skew, plans for hot keys, and raises abuse detection unprompted.
Design Typeahead SearchMedium

Return ranked search suggestions as the user types each character.

Design a Key-Value StoreHard

Build a distributed key-value store with replication and tunable consistency.