AI System Design
← All chapters
Chapter 8 6 min read

Design a URL Shortener

A URL shortener maps a long URL to a short, unique alias and redirects anyone who visits the alias. The interesting decisions are how to generate collision-free short codes, which HTTP redirect status to return, and how to serve a read-heavy workload cheaply with caching.

Architecture at a glance
  1. Client
  2. Load balancer
  3. Web servers
  4. Cache (short → long)
  5. Database
  6. Unique ID generator

Problem & requirements

The service has two jobs. Given a long URL, it returns a short one such as https://tiny.example/zn9edcu. Given a short URL, it sends the browser to the original address. Everything else, such as custom aliases, expiry, and click analytics, is optional and should be negotiated with the interviewer before you design anything.

Clarify the shape of the traffic early because it drives every later choice. A typical framing is 100 million new URLs per day, a read-to-write ratio of about 10:1, codes made only of digits and letters, and records kept for ten years. Short codes should be as short as possible, must never collide, and must not be deleted or changed once issued, since links will be printed and shared in places you cannot edit.

  • Functional: shorten, redirect, optional custom alias and expiry.
  • Non-functional: high availability for redirects, low latency, unguessable or at least non-colliding codes.
  • Scale: ~100M writes per day, ~1B reads per day.

Back-of-the-envelope estimation

Writes: 100,000,000 / 86,400 seconds is roughly 1,160 writes per second. With a 10:1 read ratio, redirects run at about 11,600 per second, and peaks may be two or three times higher. Over ten years the system stores 100M × 365 × 10 = 365 billion records.

If an average long URL plus metadata is about 100 bytes, storage is 365 × 10^9 × 100 bytes = 36.5 × 10^12 bytes, or about 36.5 TB. Double-check: 100M rows per day at 100 bytes is 10 GB per day; 10 GB × 3,650 days is 36.5 TB, which agrees. That fits in a sharded relational store or a key-value store; it is not an exotic amount of data. The read rate, not the storage, is what makes caching essential.

API design and redirect semantics

Two endpoints are enough. POST /api/v1/data/shorten takes a longUrl and returns the short URL. GET /api/v1/{shortUrl} looks up the code and answers with an HTTP redirect whose Location header holds the long URL.

The status code is a real trade-off. 301 Moved Permanently tells browsers the mapping never changes, so they cache it and later clicks skip your servers entirely. That cuts load and latency but blinds you to repeat clicks. 302 Found is treated as temporary, so every click comes back to the service, which costs more traffic but lets you count clicks, change destinations, and expire links. Choose 301 when cost dominates and 302 when analytics or control matter.

Figure 1Redirect lookup on a cache miss
Redirect lookup on a cache missBrowserLoad balancerWeb serverCacheDatabase1. GET /zn9edcu2. forward to any stateless server3. GET zn9edcu4. miss5. SELECT longURL WHERE shortURL = zn9edcu6. https://example.com/docs/2024/report7. SET zn9edcu → long URL (immutable)8. 302 Found · Location: long URL9. follow Location to the destination
With 302 Found every click returns to the service, so it can count and redirect again; with 301 the browser caches the Location and repeat clicks skip steps 1–8 entirely.

Data model and hash length

Conceptually the system is one big hash table from short code to long URL. In-memory maps do not survive restarts or scale past one machine, so the real store is a table with columns id, shortURL, and longURL, indexed on both URL columns so lookups in either direction are fast.

The code alphabet is [0-9, a-z, A-Z], which has 62 characters, so a code of length n can represent 62^n values. We need at least 365 billion. 62^6 is about 56.8 billion, which is too small, while 62^7 is about 3.52 trillion, comfortably above the target with roughly ten times headroom. Seven characters is therefore the minimum length that lasts ten years at this write rate.

Generating codes: hash + collision check vs base-62 of a unique ID

Option one hashes the long URL with CRC32, MD5, or SHA-1 and keeps the first seven characters. Truncation makes collisions possible, so the service must check the database and, on a clash, append a predefined salt string and hash again until the code is free. Each write may need several reads. A Bloom filter in front of the database can answer 'definitely not used' quickly and cut down the checks. One upside is that the same long URL always hashes the same way, which deduplicates naturally.

Option two takes a globally unique integer from an ID generator (such as a Snowflake-style service) and converts it to base 62. With the alphabet ordered 0-9, then a-z (10–35), then A-Z (36–61), ID 1,000,000 becomes 4c92, because 4 × 62³ + 12 × 62² + 9 × 62 + 2 = 953,312 + 46,128 + 558 + 2 = 1,000,000, and digit 12 maps to c. Collisions cannot happen because IDs are unique, and no lookup is needed before the insert. The costs are that code length grows as IDs grow, and sequential IDs make the next code guessable, which can be a privacy or abuse problem.

  • Hash + collision resolution: fixed length, no ID service, but extra DB round-trips on write.
  • Base-62 conversion: no collisions, simple writes, but depends on a distributed ID generator and leaks ordering.
Figure 2Hash + collision check loop
Hash + collision check loopno: freeyes: collisionrehashLong URLfrom POST /shortenHashCRC32 / MD5 / SHA-1Keep first 7 chars62^7 ≈ 3.5 trillion codesCode already used?Bloom filter, then DBAppend saltlong URL + fixed stringInsert mappingshortURL → longURL
Truncating a hash to 7 characters can collide, so every write checks for an existing code and, on a clash, rehashes with a salt. The Bloom filter answers most 'is it free?' checks without touching the database.

Shortening and redirect flows

Shortening with base 62: the server receives the long URL and first checks whether it is already stored; if so it returns the existing code, avoiding duplicates. Otherwise it fetches a new unique ID, converts it to base 62, and inserts the triple (id, shortURL, longURL). The response contains the short URL.

Redirecting dominates traffic, so it is designed around a cache. A load balancer forwards the GET to any stateless web server. The server looks up the code in a cache such as Redis; on a hit it returns the redirect immediately. On a miss it reads the database, fills the cache, and then redirects. If the code is unknown the user gets a 404. Because the mapping is immutable, cache invalidation is almost free, which is why hit rates can be very high.

Figure 3Shortening with base-62 IDs
Shortening with base-62 IDsshortenlongUrlseen?yesnext IDIDinsert rowClientPOST longUrlWeb serverstatelessURL tableid · shortURL · longURLUnique ID generatorSnowflake-styleBase-62 encodein-process: 10^6 → 4c92Return existing codeURL shortened before
The dedupe lookup comes first, so the same long URL never burns a second ID. A fresh unique ID is encoded in base 62 (e.g. 1,000,000 → 4c92), which cannot collide, so the insert needs no existence check.

Trade-offs, scaling and extras

The web tier is stateless, so it scales horizontally behind the load balancer. The database tier scales with read replicas for the read-heavy load and with sharding, typically by hash of the short code, once a single primary can no longer absorb the writes. Because each lookup is a point read on a single key, the data also fits naturally in a key-value store.

Real services add protection and insight. A rate limiter on the shorten endpoint keyed by IP or API key stops one client from exhausting the code space or flooding the system with spam links. Analytics such as click counts, referrers, and geography are gathered asynchronously, usually by having the redirect server emit an event to a queue instead of writing counters inline. Keep the redirect path short so the analytics pipeline can fail without breaking links.

Failure handling & wrap-up

Redirects must keep working even when parts of the system fail. Replicate the database, run the cache as a cluster so one node loss only reduces hit rate, and make the ID generator highly available, for example by giving each server a pre-allocated range of IDs so it can keep issuing codes during a coordinator outage. A cache stampede on a viral link is handled by request coalescing or by simply letting the first miss populate the entry.

In the interview, the strongest answers state the 62^7 arithmetic out loud, compare the two code-generation approaches honestly, and explain the 301 versus 302 choice in terms of the business goal rather than reciting definitions.

Key numbers

Write rate
100M/day ≈ 1,160/s
Read rate (10:1)
≈ 11,600/s
Records over 10 years
365 billion
Storage (100 B/record)
≈ 36.5 TB
62^6 vs 62^7
56.8 billion vs 3.5 trillion

Key terms

301 redirect
A permanent redirect that browsers cache, so repeat visits bypass the shortener.
302 redirect
A temporary redirect that sends every click back through the shortener, enabling tracking.
Base-62 encoding
Writing an integer using 62 symbols (digits plus lower- and upper-case letters) to get a compact string.
Hash collision
Two different inputs producing the same (truncated) hash value.
Bloom filter
A compact probabilistic set that can say 'definitely absent' or 'probably present' with no false negatives.
Unique ID generator
A distributed service that hands out integers guaranteed never to repeat, such as a Snowflake-style generator.
Read replica
A copy of the database that serves reads to offload the primary.

Common mistakes

  • Picking a code length without doing the 62^n arithmetic against the ten-year record count.
  • Defaulting to 301 and then promising click analytics that the browser cache will hide.
  • Truncating a hash without any collision check or retry strategy.
  • Using sequential base-62 IDs without acknowledging that codes become enumerable.
  • Writing analytics counters synchronously on the redirect path and slowing every click.
  • Forgetting to deduplicate when the same long URL is shortened repeatedly.

Further study

  • Twitter Snowflake ID generator
  • Bitly engineering blog
  • Burton Bloom, Space/Time Trade-offs in Hash Coding with Allowable Errors (1970)
  • RFC 9110 HTTP Semantics (redirect status codes)

Now practise it