Design Case StudiesIntermediate16 min read2 questions

Design a URL Shortener (TinyURL / bit.ly)

The classic warm-up question. Deceptively simple functionally, and a perfect vehicle for ID generation, extreme read/write skew and cache design.

Covers: ID generation, base62 encoding, redirect path, read-heavy caching, custom aliases, analytics, expiry

This problem is asked constantly because it is small enough to finish and rich enough to probe. The features take two minutes; the interesting part is that reads outnumber writes by roughly 100:1 and the whole system is essentially a giant, globally distributed hash table with a redirect on top.

Requirements

TypeRequirement
FunctionalShorten a long URL to a short code; redirect a short code to the original
FunctionalOptional custom alias; optional expiry
FunctionalBasic click analytics
Non-functional100 M new URLs/day; 100:1 read:write ratio
Non-functionalp99 redirect under 100 ms globally
Non-functional99.99% availability on the redirect path; shortening may be less available
Non-functionalCodes must not be guessable in sequence for private links
Out of scopeUser accounts, teams, billing, link previews

Back-of-envelope

  • Writes: 100 M/day ÷ 86,400 ≈ 1,160/s average, ~3,500/s peak. Modest.
  • Reads: 100× that ≈ 116,000/s average, ~350,000/s peak. This is the whole problem.
  • Storage: 100 M/day × 500 B ≈ 50 GB/day ≈ 18 TB/year. Ten years ≈ 180 TB, so plan for sharding or a distributed KV store.
  • Key space: base62 with 7 characters gives 62⁷ ≈ 3.5 trillion codes. At 100 M/day that is 96 years. Seven characters is the right answer.

URL shortener architecture

Note the asymmetry: the write path is ordinary, and the read path is a CDN plus cache designed to almost never touch the database.

ClientEdgeServiceCacheDatastoreQueueTap a box for detail

Flow

  • ClientCDN / edge
  • CDN / edgeLoad balancer(miss)
  • Load balancerApp servers
  • App serversID allocator(on create)
  • App serversRedis(read)
  • RedisKey-value store(miss)
  • App serversAnalytics queue(click event)
Filter
0/2 mastered
Intermediateid generationbase62hashingAsked at Amazon, Meta

30-second answer

Hashing the URL and truncating produces collisions that you must detect and resolve, and it leaks that two users shortened the same link. A global counter encoded in base62 is collision-free and compact but produces sequential, guessable codes and needs a coordination point. The practical answer is a counter with per-host pre-allocated ranges, so each server allocates from memory with no coordination, combined with a bijective scramble of the integer before encoding so the output is not sequentially guessable. For private links, generate random codes and rely on a uniqueness constraint.

AdvancedcachingcdnredirectAsked at Bitly, Amazon

30-second answer

Layer the reads: CDN at the edge, Redis in the region, then the key-value store. Link popularity follows a power law, so a few gigabytes of cache serves well over 95% of traffic and the database sees only a few thousand QPS. On the status code, 301 is permanent and cacheable by browsers and proxies, which makes redirects nearly free but means you stop seeing repeat clicks and can never change the destination. 302 is temporary and uncached, so every click reaches you - full analytics and editable destinations, at the cost of full traffic. Most commercial shorteners use 302 precisely because analytics is the product.

Showing 2 of 2 questions for design-a-url-shortener.

Check your understanding

4 questions · no sign-up, nothing stored

0/4 answered
Question 1

1.How many URLs can a 7-character base62 code represent?

Question 2

2.Why do most commercial URL shorteners return 302 rather than 301?

Question 3

3.What is the main advantage of range-allocated counter IDs?

Question 4

4.Where should click analytics be written?

Hands-on challenge

Build it - this is what you talk about in a deep-dive round.

Build a working URL shortener

Implement the design end to end, at small scale but with every architectural decision from this lesson present.

Requirements

  • Implement range-allocated IDs with a bijective scramble, and prove codes are non-sequential.
  • Implement the redirect path with Redis caching including negative caching.
  • Support custom aliases with a race-safe conditional insert and a reserved-word list.
  • Publish click events asynchronously and aggregate them into per-day counts.
  • Load test the redirect path and report cache hit rate and p99 latency.

Stretch goals

  • Add link expiry using a TTL rather than a scanning job.
  • Count unique visitors per link with HyperLogLog and compare accuracy against exact counting.