# System Design Case Study — Design a URL Shortener (Like bit.ly)

Source: https://skills101.in/learn/system-design/design-url-shortener — Skills101 free tutorial (System Design). Updated 2026-11-09.

"Design a URL shortener" (like bit.ly or tinyurl) is the most common first system design question. Let's solve it with the 6-step method.

![URL shortener: browser → load balancer → API servers, which use an ID generator (base62), a cache of hot short codes and a database mapping code to URL; estimates: 100 million new URLs a month, about 39 writes and 3,858 reads per second, 3 TB over 5 years, 62⁷ ≈ 3.52 trillion codes](https://skills101.in/learn-img/system-design/url-shortener.svg)

## Step 1 — Requirements

**Functional**
- Given a long URL, return a short one: `sk1.in/aB3xK9q`.
- Visiting the short URL redirects to the long one.
- Optional: custom aliases, expiry, click analytics.

**Non-functional**
- Very **fast redirects** (they're on every click).
- **Highly available** — a broken short link is a broken link everywhere it was shared.
- Short codes must be **unique** and not guessable in order.

## Step 2 — Estimates

Assume **100 million new URLs per month** and **100 reads for every write**:

| Quantity | Calculation | Result |
|---|---|---|
| Writes / second | 100,000,000 ÷ (30 × 86,400) | **≈ 39** |
| Reads / second | 39 × 100 | **≈ 3,858** (peak maybe 3–5×) |
| URLs in 5 years | 100 M × 12 × 5 | **6 billion** |
| Storage | 6 B × ~500 bytes | **≈ 3 TB** |

It's **read-heavy** → caching matters most.

## Step 3 — API

```text
POST /api/urls        { "long_url": "https://…", "alias": "optional" }  → 201 { "short_url": "https://sk1.in/aB3xK9q" }
GET  /{code}          → 301/302 redirect to the long URL (404 if unknown)
GET  /api/urls/{code}/stats → click counts (optional)
```

**301 vs 302:** 301 (permanent) lets browsers cache the redirect — fewer requests to you, but you can't count every click. 302 (temporary) hits your server every time — accurate analytics. Many shorteners use **302** for analytics.

## Step 4 — Generating short codes

Use **base62** characters: `a–z`, `A–Z`, `0–9` = 62 symbols.

| Length | Possible codes |
|---|---|
| 6 | 62⁶ ≈ 56.8 billion |
| 7 | 62⁷ ≈ **3.52 trillion** |

7 characters is plenty for 6 billion URLs.

**Option A — counter + base62 (recommended):** a unique ID generator gives 1, 2, 3, … and we convert the number to base62.

```python
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def to_base62(n):
    if n == 0:
        return ALPHABET[0]
    out = []
    while n:
        n, r = divmod(n, 62)
        out.append(ALPHABET[r])
    return "".join(reversed(out))

print(to_base62(125), to_base62(3_844), to_base62(56_800_235_583))
```

```text
21 100 ZZZZZZ
```

To avoid one counter becoming a bottleneck, give each server a **range** of IDs (server 1 gets 1–1,000,000; server 2 the next million…), or use a Snowflake-style ID. To make codes non-sequential, shuffle/encode the ID before converting.

**Option B — hash the URL** (MD5/SHA) and take 7 characters: simple, but collisions must be checked and handled.

## Step 5 — Data model and storage

```text
urls(code PRIMARY KEY, long_url, user_id, created_at, expires_at)
```

A simple key → value lookup. Either a SQL database (with `code` as the primary key) or a key-value NoSQL store works; at 3 TB, shard by `code` (hash sharding) when one machine isn't enough.

## Step 6 — Making redirects fast

- **Cache** hot codes in Redis: popular links get most clicks (the 80/20 rule). Caching 20% of daily reads needs only tens of GB of RAM.
- Read **replicas** for database reads.
- **CDN / edge** redirects for the most popular links.
- Track clicks **asynchronously**: push a click event to a queue; aggregate later.

## Trade-offs to mention

- 301 (fast, cached) vs 302 (accurate analytics).
- Counter-based codes (no collisions, needs ID coordination) vs hashing (no coordination, collision checks).
- Abuse prevention: rate limit creation, scan for malware/phishing links.

## Quick recap

- Read-heavy: ~39 writes/s vs ~3,858 reads/s; 3 TB over 5 years.
- API: POST to create, GET /{code} to redirect.
- Base62, 7 chars → 3.5 trillion codes; counter ranges or Snowflake IDs.
- Cache hot codes, replicas for reads, async click analytics.


Practice quiz and free certificate: https://skills101.in/learn/system-design/design-url-shortener