Sweaty Imposter

Design TinyURL

System DesignRead HeavyMid3 min readUpdated

The classic warm up prompt. The boxes are easy; the interview is decided by your read path math and how you generate codes that never collide.

1 · Requirements

The Prompt

Design a URL shortener supporting ~100M new URLs/day and ~10B reads/day. Short links must be 7 characters and resolve in <100ms globally.

Functional

  • Shorten a long URL, with an optional custom alias and expiry
  • Redirect a short code to its long URL
  • Owners can delete a link and see basic click stats

Non-Functional

  • Redirects resolve in under 100 ms worldwide under heavy read load
  • Codes are 7 characters, never collide, and never get reused
  • Reads stay up even when writes degrade

2 · Estimates

Drill This Phase First

Show the Answer and Estimates

Answer

About 290,000 a second

10B divided by 86,400 seconds is about 115,000 a second on average, and 2.5x that is about 290,000.

Writes
100M new URLs a day, about 1,200 a second on average and 3,000 at a 2.5x peak
Reads
10B redirects a day, about 115,000 a second on average and 290,000 at peak
Ratio
About 100 reads per write, so the design pivots on the read path
Storage
About 500 bytes a record for five years: roughly 90 TB of metadata
Cache
About 20% of URLs serve 80% of traffic: roughly 35 GB across a Redis cluster
Bandwidth
A redirect is a 302 and a tiny body. The CDN serves hot codes and ships its edge logs to the click stream, so the origin sees under 5% of reads

3 · API

POST /shorten { long_url, custom_alias?, ttl? } → { short_url, expires_at? }
GET /:short_id → 302 redirect to long_url (cache-aside lookup). Browsers cache a 301 and skip the service on repeat visits, which hides those clicks from analytics; pick 301 only when you don't count clicks and the destination never changes.
DELETE /:short_id (owner-authenticated) → 204
GET /:short_id/stats → { clicks, top_countries, ts_series } (async pipeline)

Data Model

urls (KV, partitioned by short_id):
  short_id (PK)  : string, 7 chars base62
  long_url       : string
  owner_id       : string?      nullable for anonymous
  created_at     : timestamp
  expires_at     : timestamp?
  click_count    : counter      eventually consistent via stream

users (SQL, optional):
  user_id (PK)   : uuid
  email          : string
  api_quota      : int          requests/day

4 · High-Level Design

Reads try the CDN, then Redis, then the store. Writes take a fresh ID, encode it, and write through to the store.

5 · Deep Dives

Short Code Generation

Hand out ranges from a central counter so each API server encodes its own block in base62. Seven characters gives about 3.5 trillion codes. Hashing the long URL avoids the counter but forces a collision check on every write.

Hot Keys and the 301 Question

A 301 lets browsers cache the redirect and saves load, but you lose click counts and later edits never reach that browser. A 302 keeps every click visible at the cost of more reads. Most designs pick 302 and lean on the CDN for hot codes.

6 · Wrap Up

Name the next three risks out loud: abuse and rate limiting on the write path, cleanup for expired links, and a second region for reads.

The Short Version

Heavy read skew makes a CDN and cache-aside critical. Base62-encode an auto-increment ID or a hash, look up short to long in a key-value store, and shard by a hash of short_id.