Module 12 · Lesson 01

Design a URL shortener

The warm-up problem. Small surface area, one interesting property: reads outnumber writes by a hundred to one.

Beginner 8 min read Updated 2026-11-02

What you will be able to do

  • Generate short codes with no collision-check bottleneck
  • Size storage and cache from stated assumptions
  • Keep redirect latency low with a cache-first read path
  • Handle expiry and custom aliases without a full scan

Requirements

  • Functional: create a short link for a URL, redirect on visit, optional custom alias and expiry, basic click counts.
  • Non-functional: redirect under 50 ms at p99, 100:1 read-to-write ratio, links live for years, unavailable links return 404 or 410.
  • Out of scope: user accounts, dashboards, abuse scanning beyond a denylist.

Estimates

writes  100M new links/day  = ~1,200/s
reads   100x writes          = ~120,000/s
row     ~500 B (url + code + meta)
storage 100M x 500 B         = 50 GB/day, ~18 TB/year
cache   20% hot links, 7 days = ~140M rows, ~70 GB
codes   62^7 = 3.5 trillion  -> 7 chars is plenty

API

EndpointPurposeNotes
POST /linksCreate a short linkBody: url, optional alias, optional ttl
GET /{code}Redirect301/302 to the target, no JSON
DELETE /links/{code}RevokeOwner-scoped, marks inactive
GET /links/{code}/statsClick countsEventually consistent

Use 302 by default so clicks keep flowing through your service for analytics; 301 lets browsers cache the redirect permanently, which is faster but hides future traffic and makes revocation unreliable.

Generating the code

Reserve a range
Each app instance takes a block of 10,000 ids from a counter service or a database sequence.
Encode in Base62
Turn the integer into 7 characters from [a-zA-Z0-9]. Reversible, no collisions inside a block.
Insert with a unique constraint
The database is the final arbiter; a duplicate insert is retried with the next value.
Custom aliases separately
A unique index on the alias column, with a reserved-word denylist for /admin, /api and friends.
Sequential codes are guessable
Base62 of an incrementing counter lets anyone enumerate every link. If privacy matters, use a random 7-character code or encrypt the id before encoding — and say which you chose and why.

Read path and bottlenecks

The redirect path is: edge cache, then a shared Redis cache, then the database. With a 90% cache hit rate, 120k reads/s becomes 12k database reads/s, which a single primary with read replicas handles comfortably. Cache misses are coalesced so a popular new link does not stampede.

GET /abc1234
  edge cache hit?  -> 302 (most requests end here)
  redis GET code   -> hit: 302, refresh edge TTL
  db select        -> populate redis, 302
  missing/expired  -> 404 / 410

click event -> append to stream (async) -> aggregation job

Analytics must never be on the redirect path. Emit an event to a queue and aggregate offline; losing a few click events is acceptable, adding 5 ms to every redirect is not. Expiry is stored as a column and checked on lookup, with a background job deleting in batches using an index on expiry time.

Close the loop
"Reads are cache-first at the edge and in Redis, so the database sees roughly a tenth of traffic. Codes come from pre-allocated counter blocks, analytics is an async event stream, and expiry is a filtered column scan in batches."

Worth remembering

  • 65 billion codes fit in seven Base62 characters, so length is arithmetic, not taste.
  • A pre-allocated counter range per instance avoids collisions without coordination per request.
  • Redirects must be cached at the edge; analytics should be asynchronous.
  • Expiry belongs in the lookup, not in a nightly cleanup job that races with reads.

Checkpoint

Answer all 3 questions to finish the lesson.
1. How many unique codes fit in seven Base62 characters?
62^7 ≈ 3.5 × 10^12, far beyond the 100M/day the design targets.
2. Why prefer 302 over 301 for short links?
A permanent redirect is cached by the browser, so later clicks never reach you and a revoked link may still resolve.
3. Where should click analytics be recorded?
Analytics must not add latency or fail the redirect. Emitting an event keeps the critical path minimal.