Skip to content

Case Study 1 — Design URL Shortener

Problem: Design a URL shortening service like TinyURL or bit.ly that converts long URLs into short, shareable links and redirects users to the original URL.


TypeRequirement
FunctionalGenerate short URL, redirect to original URL, optional custom alias, analytics (click count)
Non-Functional< 10ms redirect latency, 99.99% availability, support 100M+ URLs, handle 10K writes/sec, 100K reads/sec
ConstraintsShort URL must be as short as possible (6-8 characters), URLs never expire

MetricCalculationResult
New URLs per day10M~115 URLs/sec
Redirects per day1B~11,000 redirects/sec
Storage (10 years)10M × 365 × 10~36.5B URLs
Storage per URLEntry: 500 bytes (short + long + metadata)~18 TB total
Read/Write ratio100:1Cache-friendly

Interact with the stages below to see how the system design scales from a single server to serving millions of users.


1. Key Generation

ApproachProsCons
Base-62 encoding of IDShort keys (7 chars), predictableSequential keys can be guessed
MD5/SHA-1 hashRandom-lookingCollision handling, fixed length
Snowflake-style IDUnique, ordered, distributedComplex
Pre-generated keysFast (no computation at write time)Need key DB, cache

Chosen: Base-62 encoding of a unique 64-bit ID → 7 character short URL. 62^7 = 3.5 trillion combinations.

2. Redirection — HTTP 301 vs 302

StatusBrowser BehaviorUse Case
301 (Permanent)Cached by browser, no subsequent request to our serviceURLs that never change
302 (Temporary)Browser asks our service every timeNeed analytics (count clicks)

Chosen: 302 redirect for analytics; 301 for custom URLs guaranteed permanent.


sequenceDiagram
participant User
participant Web as Web Server
participant Cache as Redis Cache
participant DB as Database
User->>Web: POST /shorten (long URL)
Web->>Web: Generate unique key
Web->>DB: INSERT (key, long_url, created_at)
DB-->>Web: OK
Web->>Cache: SET key → long_url
Web-->>User: 201 Created (short URL)
User->>Web: GET /abc1234
Web->>Cache: GET abc1234
alt Cache Hit
Cache-->>Web: Original URL ✅
else Cache Miss
Web->>DB: SELECT long_url WHERE key = abc1234
DB-->>Web: Original URL
Web->>Cache: SET abc1234 → long_url
end
Web->>Web: Increment click count (async)
Web-->>User: 302 Redirect to original URL

CREATE TABLE url_mappings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
short_key VARCHAR(10) UNIQUE NOT NULL,
original_url TEXT NOT NULL,
user_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NULL,
INDEX idx_short_key (short_key),
INDEX idx_user_id (user_id)
);
CREATE TABLE click_events (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
short_key VARCHAR(10) NOT NULL,
clicked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_agent TEXT,
ip_address VARCHAR(45),
country VARCHAR(100),
INDEX idx_short_key (short_key),
INDEX idx_clicked_at (clicked_at)
);

AspectApproachTrade-off
DatabaseShard by key hashComplex queries across shards
CacheRedis cluster, LRU evictionCache misses for cold URLs
AnalyticsClick events in separate DB, batch to warehouseEventual consistency for stats
Key generationPre-generate keys in batch, keep bufferWarm pool of pre-generated keys
Rate limitingPer-user and per-IP limits on creationAdded complexity

  • URL shortener is a simple read-heavy key-value store: short key → long URL
  • Base-62 encoding of a numeric ID gives short, unique keys
  • 302 redirect for analytics, 301 for permanent URLs
  • Heavy caching (Redis) for redirects (100:1 read/write ratio)
  • Asynchronous logging of click events — don’t block the redirect
  • Scale by sharding the database and adding cache nodes