Design TinyURL
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/day4 · High-Level Design
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
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.