Under the Hood
Case studies

Design a URL shortener

The classic warm-up prompt, and a perfect one because it looks trivial and hides real decisions. Worked through the framework: it's a massively read-heavy key-value lookup, which points straight at caching and replicas; the only genuinely interesting question is how you generate short unique codes at scale without collisions, and whether the redirect is a 301 or a 302. This case study applies the interview method end to end and maps each decision back to the lesson that explains it.

Design a URL shortener

"Design a URL shortener" (TinyURL, bit.ly) is the canonical opening prompt, and it's a good one precisely because it seems trivial — it's a hash map, right? — while hiding a few decisions that reveal whether you understand scale. We'll run it through the framework exactly as you would out loud.

Requirements

Functional: given a long URL, return a short one; visiting the short URL redirects to the long one. Clarify and mostly defer: custom aliases (nice-to-have), link expiry (maybe), click analytics (maybe), user accounts (out of scope for core). The core is two operations: shorten and redirect.

Non-functional — the ones that shape everything: this is overwhelmingly read-heavy (people click short links far more than they create them — assume 100:1 reads:writes), latency-critical on redirect (a slow redirect is a broken-feeling link), and needs high availability (a shortener that's down breaks every link ever made). Short codes should be, well, short. That read-heavy + latency-critical + high-availability profile is the design brief.

Estimates

Say 100M new URLs/day. That's ~1,200 writes/sec (100M ÷ ~86,400 sec) — modest. At 100:1, redirects are ~120,000/sec — that's the number that matters. Storage: 100M/day × 365 × 5 years × ~500 bytes ≈ low terabytes — fits comfortably in a sharded database or a KV store, not a scary number. The read/write asymmetry is the whole story: ~1k writes/s is trivial; ~120k reads/s at low latency is the problem to solve, and it points immediately at caching and read replicas.

High-level design

  • API: POST /shorten {long_url}{short_url}; GET /{code} → HTTP redirect to the long URL.
  • Data model: essentially one mapping — code → long_url (plus created_at, optional expiry/owner). A key-value shape.
  • Flow: client → load balancer → app servers → cachedatabase. Redirects check the cache first, fall through to the DB on a miss.

Deep-dive 1: generating the short code

This is the interesting part, and the interviewer will push here. Three approaches, with real trade-offs:

  • Hash the URL (e.g. take the first 7 chars of a base62-encoded hash). Simple and stateless, but hashes collide, so you must check "is this code taken?" on every write and rehash on a hit — and identical URLs hash to the same code, which may or may not be desired.
  • Counter + base62 encode. Keep a global counter; each new URL gets the next integer, encoded into base62 ([a-zA-Z0-9] — so 62⁷ ≈ 3.5 trillion codes in 7 characters). No collisions by construction. Downsides: codes are sequential and enumerable (anyone can walk aaaaaab, aaaaaac… and discover everyone's links — a privacy/security issue), and the global counter is a coordination point.
  • Key Generation Service (KGS). Pre-generate a large set of random unique codes offline, store them, and hand them out on demand. The write path becomes "grab a pre-made unused key" — no collision check, no hot counter, and codes are unpredictable. The KGS is the clean production answer; its cost is that it's a component to run (and a potential single point of failure — you replicate it and hand out key ranges to app servers so it's not on every write's critical path).

Naming these three and picking the KGS "because it avoids collision-checking on the hot path and produces unguessable codes, at the cost of running a key service" is exactly the trade-off-shaped answer the framework rewards.

Deep-dive 2: the redirect, and why 301 vs 302 matters

GET /{code} returns an HTTP redirect — and which status code is a real decision:

  • 301 (permanent) — browsers and proxies cache it, so subsequent clicks skip your server entirely and go straight to the long URL. Great for load (fewer hits), terrible for analytics (you never see the repeat clicks) and for expiry (the cached redirect outlives your record).
  • 302 (temporary)not cached, so every click comes back to your server. This is what you want if you need click analytics or expiry/revocation — at the cost of serving every redirect.

The choice is a direct trade of load against control. If analytics matter, 302; if you just want links to be fast and cheap, 301. Saying why you'd pick one is the point.

Deep-dive 3: scaling the reads

The 120k reads/sec is solved by the read-heavy playbook, and this system is the ideal case for it because of one lovely property: a code→URL mapping is immutable once created. So it's the perfect cache candidate — you cache aggressively with long TTLs and never face the invalidation problem (the hard part of caching) because the value never changes. Hot links live in cache and never touch the database. Below the cache, read replicas absorb misses, and because the data is a pure key-value shape, sharding by code is straightforward (the code is a perfect shard key — every read is a point lookup by it, so reads stay single-shard). Immutability makes every scaling tool easy here.

Go deeper

Check yourself

Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.

  1. What is the read/write ratio of a URL shortener, and how does recognizing it immediately dictate the high-level design? Which of your estimated numbers is the one that actually matters?
  2. Compare the three code-generation strategies (hash, counter+base62, KGS). Give the specific weakness of each, and articulate the KGS choice as a trade-off.
  3. Why is a sequential counter encoding a security/privacy concern, and how does base62 relate to code length (roughly how many codes fit in 7 chars)?
  4. Explain the 301-vs-302 decision as a trade of load against control. When would you pick each, and what does 301 cost you that 302 preserves?
  5. A code→URL mapping is immutable. Explain why that single property makes caching, replication, and sharding all easy here — especially why it removes the hardest part of caching.
  6. How is a Fable group-invite link the same shape as a URL shortener, and why must its code be unguessable rather than sequential?