Skip to content

Module 07 // Case studiesLesson 26 / 3425 min read

Design a URL shortener

A URL shortener turns a long link into a short code and redirects anyone who visits the code. It's read-heavy, needs unique short codes, and must redirect in milliseconds.

Think of it like this

Analogy

A coat-check desk at a big event. You hand over a long, bulky coat (the long URL) and get back a tiny numbered ticket (the short code). Anyone with the ticket can get the coat. The desk must never hand out the same ticket number twice, and it should find coats quickly, because the line for picking them up is much longer than the line for dropping them off.

The problem it solves

Long URLs are awkward to share in messages, on printed material and in social posts. A shortener gives you something like sho.rt/a8Kz2Qp. The hard parts aren't the idea, they're the numbers: generating billions of unique codes across many servers, and serving redirects fast for a link that suddenly goes viral.

A real example

Example

Short links in a marketing text message

A shop sends a sale text to 2 million customers with a link like sho.rt/x7K2a. Within ten minutes, hundreds of thousands of people tap it.

Each tap is a lookup from code to long URL. The answers sit in a cache, so most redirects never touch the database and finish in a few milliseconds.

Every click also needs counting for the marketing report. Instead of updating a database row on every tap, the redirect service sends a click event to a queue, and a worker adds up the counts in batches. The link expires 30 days after the sale, and the code can be reused later.

How it works

1. Requirements

Functional: create a short link for a long URL (optionally with a custom alias and an expiry); visiting the short link redirects to the long URL; basic click analytics.

Non-functional: redirects are fast (p99 under about 50 ms server-side) and highly available; codes are unique and not trivially guessable; durable (a link should work for years).

2. Estimates

Assume 100 million new links a month and 100 reads for every write.

Loading the playground…
  • Writes: 100 M ÷ (30 × 86,400 s) ≈ 40 per second; reads ≈ 4,000 per second on average, maybe 10–20 thousand at peak.
  • Storage: about 500 bytes per link × 1.2 billion a year ≈ 600 GB a year, a few TB over five years with replicas.
  • This is read-heavy and small. Caching will handle most reads.

3. API

POST /api/links          {"url": "https://example.com/very/long", "alias": "spring-sale", "expiresAt": null}
→ 201 {"code": "a8Kz2Qp", "shortUrl": "https://sho.rt/a8Kz2Qp"}
 
GET /a8Kz2Qp
→ 302 Location: https://example.com/very/long

Creating accepts an idempotency key so client retries don't create duplicate links.

4. Generating short codes

Base62 (0-9a-zA-Z) with 7 characters gives about 3.5 trillion codes.

ApproachHowGoodBad
Hash the URLFirst 7 chars of a hash of the URLSame URL → same codeCollisions need handling; can't have two codes for one URL
Random code7 random base62 chars, insert if not takenUnguessable, simpleNeeds a uniqueness check per insert
Counter rangesEach server grabs a block of IDs (say 1,000) from a coordinator, then encodes each IDNo collisions, no per-insert checksSequential codes are guessable unless scrambled

A solid choice: counter ranges per server, with each number scrambled by a reversible permutation before base62 encoding. That's unique without coordination per request, and not enumerable. Custom aliases are checked with a conditional insert.

5. Data model

A key-value store fits perfectly: code → {longUrl, createdAt, expiresAt, ownerId}. Use the code as the partition key (shard key); it's evenly distributed and every read is a single lookup.

6. High-level design

Reference architectureURL shortener

Architecture
Loading diagram…
Redirects are cached at the edge and in memory; the key-value store is the source of truth; click events go to a stream for analytics.
  • Users to Route 53: lookup
  • Users to CloudFront
  • CloudFront to Application Load Balancer: miss
  • Application Load Balancer to ECS on Fargate
  • ECS on Fargate to ElastiCache for Valkey
  • ECS on Fargate to DynamoDB
  • ECS on Fargate to Kinesis Data Streams
  • Kinesis Data Streams to Amazon S3

On a redirect: the CDN may answer directly if the redirect is cacheable. Otherwise the app checks the cache (hot codes are tiny and fit easily in memory), falls back to the database on a miss, and returns a redirect. Each click event goes to a stream, asynchronously, so analytics never slows the redirect.

7. Deep dives

  • 301 vs 302: 301 lets browsers and CDNs cache the redirect, which saves load but hides repeat clicks. Use 302 if analytics or editable destinations matter.
  • Viral links: cache at the edge with a short TTL, plus the in-memory cache. Request coalescing stops a stampede on a miss.
  • Abuse: shorteners attract spam and phishing. Check URLs against safe-browsing lists on creation, rate-limit creation per user and IP, and support takedowns.
  • Expiry: a TTL on the record, plus a background job to clean up.

Trade-offs

What you gain

  • Very simple data model and access pattern; scales by sharding on the code.
  • Read-heavy, so caching makes it cheap and fast.
  • Analytics are decoupled through a stream.

What it costs you

  • Cached 301s make click counts approximate.
  • Code generation needs care to be unique, fast and unguessable.
  • A public service attracts abuse that needs constant work.

When not to use it

  • For internal links between your own pages, you don't need a separate service; use your own routing.
  • Don't add a relational database with joins here unless you need rich ownership features; the core is pure key-value.

In the cloud

NoSQL databasesEvery major cloud sells this as a managed service, so you configure it rather than build it.

AWS3

Azure1

Google Cloud2

Interview questions

Try answering out loud first, then open the model answer.

How do you guarantee short codes are unique across many servers?

Model answer

Either coordinate in batches or check on insert. Batched: a small coordinator (a database sequence or a consensus store like etcd) hands each app server a range of IDs; the server uses them locally, so uniqueness is guaranteed without a per-request call. To avoid guessable codes, apply a reversible scramble before base62 encoding. Check-on-insert: generate a random code and insert with a condition that it doesn't exist, retrying on the rare collision. Both work; ranges have no hot path to a shared component.

How would you handle 1 million redirects per second for a viral link?

Model answer

Let the edge absorb it. Serve the redirect from the CDN with a short cache TTL (or a 301 if analytics can be approximate), so nearly all requests never reach the origin. Behind it, keep the code in an in-process cache on every app server and in the shared cache, with request coalescing on misses. Make click logging asynchronous and batched. Auto-scale the stateless app tier. The database sees almost no load for that link.

How would you add click analytics without slowing redirects?

Model answer

The redirect handler publishes a small click event (code, timestamp, referrer, coarse location from the IP, user agent) to a stream and returns immediately, without waiting. Consumers aggregate counts per code per minute into a time-series or analytics store and archive raw events to object storage. Dashboards read the aggregates. If the stream is briefly unavailable, the app drops or buffers events rather than failing redirects.

Quiz

Pick an answer to see whether it’s right and why.

  1. 01How many unique codes do 7 base62 characters give?
  2. 02Should the redirect be 301 (permanent) or 302 (temporary)?
  3. 03Why is a simple counter (1, 2, 3…) encoded in base62 risky as the only way to make codes?
  4. 04Redirects outnumber new links 100 to 1. What helps most?
  5. 05How do you count clicks without slowing down redirects?
  6. 06Two users shorten the same long URL. Should they get the same code?

0 of 6 answered

Key terms

Base62
Writing numbers with 0–9, a–z and A–Z, so short strings cover huge ranges (7 characters ≈ 3.5 trillion values).
Key-value store
A database that stores values under unique keys, like a giant dictionary. Very fast lookups by key.
Cache
A small, fast store that keeps copies of data that is expensive to fetch, so repeat requests are quicker.
CDN
Content delivery network. Servers around the world that keep copies of files close to users.
Shard key
The field used to decide which shard a record lives on, such as user ID.
Idempotency
Doing the same operation twice has the same effect as doing it once, which makes retries safe.

Progress is saved in this browser only.