🔥 0
0
Lesson 9 of 10 25 min +200 XP

Case Study: URL Shortener

Let's design a URL shortener like Bitly or TinyURL. This is a classic system design interview question that touches on every concept we've learned.

We'll follow the Architect's Framework: Clarify → Estimate → Design → Deep-Dive.

Step 1: Clarify Requirements

Functional Requirements

What does the system need to do?

  • Given a long URL, generate a unique short URL
  • Given a short URL, redirect to the original long URL
  • Short URLs should expire after a configurable time (optional)
  • Track analytics: click counts, referrers, etc. (optional)

Non-Functional Requirements

How well does the system need to perform?

100M
URLs created/month
100:1
Read:Write ratio
<100ms
Redirect latency (P99)
99.9%
Availability

Step 2: Back-of-Envelope Estimation

Traffic

URLs created per month: 100 million
URLs created per second: 100M / (30 * 24 * 3600) ≈ 40 writes/sec

Read:Write ratio: 100:1
Redirects per second: 40 * 100 = 4,000 reads/sec

Storage

URL length (average): 500 bytes
Short code: 7 bytes
Metadata: ~100 bytes
Total per URL: ~600 bytes

URLs stored for 5 years: 100M * 12 * 5 = 6 billion URLs
Storage needed: 6B * 600 bytes = 3.6 TB

Summary

Metric Value
Writes/sec ~40
Reads/sec ~4,000
Storage (5 years) ~4 TB
Insight: This is a read-heavy system. We need to optimize for read performance.

Step 3: High-Level Design

URL shortener high-level design: Client, Load Balancer, API servers, Cache, Database, Analytics

API Design

POST /api/shorten
Body: { "url": "https://example.com/very/long/url" }
Response: { "short_url": "https://short.ly/abc1234" }

GET /{short_code}
Response: 302 Redirect to original URL

Step 4: Deep Dive

Short Code Generation

How do we generate unique 7-character codes?

#### Option 1: Hash the URL

short_code = base62(md5(long_url))[:7]
Problem: Collisions. Different URLs might hash to same short code.

#### Option 2: Counter + Base62

# Global counter: 1, 2, 3, 4...
short_code = base62(counter)
Problem: Predictable URLs (security issue), counter becomes bottleneck.

#### Option 3: Pre-generated Keys (Recommended)

Generate keys offline, store in a key service. Each API server grabs a batch.

Key generation service: pre-generates keys, API servers fetch batches
Pros: No collision checking, no bottleneck Cons: Need to manage key service

Base62 Encoding

Why 7 characters in Base62?

Base62 characters: a-z (26) + A-Z (26) + 0-9 (10) = 62

7 characters: 62^7 = 3,521,614,606,208 (3.5 trillion)

At 100M URLs/month = 1.2B/year
Time to exhaust: 3.5T / 1.2B = 2,917 years
7 characters is plenty.

Database Schema

CREATE TABLE urls (
    short_code VARCHAR(7) PRIMARY KEY,
    long_url TEXT NOT NULL,
    user_id UUID,
    created_at TIMESTAMP DEFAULT NOW(),
    expires_at TIMESTAMP,
    click_count INT DEFAULT 0
);

-- Index for expiration cleanup
CREATE INDEX idx_expires ON urls(expires_at);
Choice: Key-value store (like DynamoDB, Redis) would work well here since we're doing simple key lookups.

Caching Strategy

Cache-aside pattern for reads:
def redirect(short_code):
    # 1. Check cache
    long_url = cache.get(short_code)
    if long_url:
        return redirect_302(long_url)  # Cache hit!

    # 2. Cache miss - check database
    long_url = db.get(short_code)
    if not long_url:
        return 404

    # 3. Update cache
    cache.set(short_code, long_url, ttl=3600)

    return redirect_302(long_url)
Expected cache hit rate: 80%+ (popular URLs get clicked repeatedly)

With 80% hit rate:

  • 4,000 reads/sec × 0.2 (misses) = 800 DB queries/sec

Analytics (Async)

Don't block redirects to record analytics. Use a message queue:

def redirect(short_code):
    long_url = get_url(short_code)  # From cache or DB

    # Async analytics
    queue.publish({
        'short_code': short_code,
        'timestamp': now(),
        'referrer': request.headers.get('Referer'),
        'user_agent': request.headers.get('User-Agent'),
        'ip': request.ip
    })

    return redirect_302(long_url)

Workers consume from the queue and batch-write to an analytics database.

Complete Architecture

Complete URL shortener architecture with CDN, Load Balancer, APIs, Cache, Database, Key Service, and Analytics pipeline

Design Decisions Summary

Decision Choice Why
Short code generation Pre-generated keys No collisions, no bottleneck
Database Key-value store Simple lookups, easy to shard
Caching Cache-aside with Redis Read-heavy workload
Analytics Async via queue Don't block redirects
Scaling Horizontal + sharding Stateless API, shardable DB

Key Takeaways

  • Start with requirements - both functional and non-functional
  • Do the math - back-of-envelope calculations guide design decisions
  • Optimize for the common case - reads dominate, so cache aggressively
  • Decouple non-critical paths - analytics shouldn't slow down redirects

Next up: HLD Assessment - Test your knowledge across all topics.

🧠 Quick Quiz

Test your understanding of this lesson.

1

Why use Base62 encoding for short URLs instead of Base10?

2

What's the main advantage of pre-generating short codes?

3

For a URL shortener, which is more critical - read latency or write latency?

API Design & Rate Limiting