Walkthrough — Design a URL Shortener (TinyURL / Bitly)¶
The single most common system design interview question. This walks the full four-step approach from the Introduction.
Step 1 — Requirements, constraints, and assumptions¶
Clarifying questions¶
- Traffic volume? (e.g., "100 million URLs generated per day")
- How short? (as short as possible)
- Custom aliases allowed?
- Expiry / TTL on links?
- Analytics (click counts, geography) needed?
Functional requirements¶
- Shorten: given a long URL, return a unique short alias.
- Redirect: given a short URL, redirect to the original.
- (Optional) custom alias, expiration, analytics, user accounts.
Non-functional requirements¶
- Highly available and very low-latency redirects.
- Read-heavy (roughly 100:1 read-to-write).
- Short codes must be unique and shouldn't collide.
Back-of-the-envelope¶
| Metric | Estimate |
|---|---|
| New URLs / day | 100 million |
| Write rate | 100M / 86,400 s ≈ 1,157 writes/sec |
| Read rate (100:1) | ≈ 115,700 reads/sec |
| Rows over 5 years | 100M × 365 × 5 ≈ 182.5 billion |
| Storage (≈500 B/row) | ≈ 91 TB |
| Cache (hot 20%) | ≈ 20M entries in RAM |
Step 2 — High-level design¶
┌──────────────┐
Client ──────────▶ │ DNS / CDN │
└──────┬───────┘
│
┌──────▼───────┐ ┌──────────────┐
│ Load Balancer│──────▶│ App servers │ (stateless)
└──────┬───────┘ └──────┬───────┘
│ │
write │ │ read
▼ ▼
┌────────────┐ ┌────────────┐ miss ┌───────────┐
│ Key pool / │ │ Redis │──────────▶ │ Sharded │
│ generator │ │ (cache) │ │ SQL DB │
└────────────┘ └────────────┘ └───────────┘
Two distinct paths:
- Write path:
POST /shorten→ generate a code → store in DB (and warm cache). - Read path:
GET /{code}→ check cache → on miss, read DB → return301.
Step 3 — Core components¶
3.1 Short-code generation¶
Three common approaches, each with trade-offs:
| Approach | How | Pros | Cons |
|---|---|---|---|
| Hash + Base62 | MD5/SHA of URL, take first 7 chars, encode Base62 | No coordination | Collisions (mitigate by retry / appending) |
| Base62 of a counter | Encode a unique 64-bit ID (Snowflake) to Base62 | Collision-free, short | Needs a distributed ID generator |
| Pre-generated key pool | Offline service generates keys, hands them out | Fast writes, no in-line collision | Key pool can exhaust; more moving parts |
Base62 alphabet: [a-z A-Z 0-9] = 62 chars. A 7-char code gives 62^7 ≈ 3.5
trillion combinations.
Collision handling: use a unique DB constraint; on collision, retry with a different salt/counter. This is why the counter approach is usually preferred — it can't collide.
3.2 Data model (SQL is a natural fit)¶
CREATE TABLE urls (
short_code VARCHAR(10) PRIMARY KEY, -- indexed, unique
long_url TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP, -- optional
user_id BIGINT -- optional
);
Why SQL here: structured data, strict schema, need for uniqueness and fast indexed lookups.
3.3 Redirect path¶
- Receive
GET /{short_code}. - Look up
short_code(cache first, then DB). - Return 301 (permanent — cacheable) or 302 (temporary — if you want to count every click for analytics).
3.4 API¶
POST /shorten
{ "long_url": "...", "custom_alias": "optional", "expiration": "optional" }
→ { "short_url": "https://short.ly/abc123" }
GET /{short_code}
→ 301 Location: https://original.example.com/very/long/url
Step 4 — Scale the design¶
Caching (read path is hot)¶
- Use cache-aside with Redis: cache
short_code → long_url. - The 80/20 rule: a small set of URLs drive most traffic — cache them.
- TTL for freshness; evict when a URL expires or changes.
Sharding (write volume grows)¶
-
Shard the
urlstable byshort_code(e.g., first 2–3 chars) or via consistent hashing on the code. -
A distributed ID generator (Snowflake) gives collision-free, roughly sortable IDs to encode as Base62 codes.
Replication & availability¶
- Master-slave replication to serve the read-heavy load from replicas.
- Stateless app servers behind a load balancer for horizontal scaling.
Hardening¶
-
Rate limiting to prevent abuse (see 02 Latency vs Throughput).
-
Validate/sanitize inputs; prevent open-redirect and SSRF (see 16 Security).
-
Analytics as an async side-channel (see 14 Asynchronism).
Key trade-offs to mention¶
- Hash vs counter: collision risk vs the need for a coordinated ID generator.
- 301 vs 302: cacheability vs per-click analytics.
-
SQL vs NoSQL: uniqueness + indexed lookups favor SQL; a KV store also works for the pure redirect path.
-
Cache vs DB: cache absorbs the hot read load but introduces staleness and invalidation complexity.