System Design Problems
Design a URL Shortener
A URL shortener converts a long URL into a short, unique alias. The short URL redirects to the original long URL. Services like TinyURL, bit.ly, and t.co handle billions of redirects daily with sub-millisecond latency.
- Core Functionality â Generate short URLs and redirect with minimal latency
- Scale â Handle billions of short URLs and high read-to-write ratios
- Availability â Redirect service must be highly available (reads >> writes)
The challenge is balancing simplicity with scalability: generating unique IDs efficiently while maintaining fast lookups across a global infrastructure.
Requirements
Functional Requirements
- Given a long URL, generate a unique short URL
- Given a short URL, redirect to the original URL
- Users can optionally set a custom short URL alias
- URLs expire after a configurable TTL
- Support analytics (click count, referrer, geographic data)
Non-Functional Requirements
- Latency: Redirect in < 10ms (99th percentile)
- Availability: 99.99% uptime (redirect is read-heavy)
- Durability: Short URLs must never be lost
- Scalability: 100M new URLs/month, 10:1 read-to-write ratio
Back-of-the-Envelope Estimation
API Design
POST /api/v1/urls
Request: { "long_url": "https://...", "custom_alias": "my-link", "ttl_days": 30 }
Response: { "short_url": "https://sho.rt/abc123", "expires_at": "2026-07-20" }
GET /{short_code}
Response: 301/302 Redirect to original URL
DELETE /api/v1/urls/{short_code}
Response: { "status": "deleted" }
GET /api/v1/urls/{short_code}/analytics
Response: { "clicks": 1523, "unique_visitors": 892, "top_referrers": [...] }
High-Level Architecture
Detailed Design
ID Generation Strategies
The critical design decision is how to generate unique short identifiers:
| Strategy | Pros | Cons |
|---|---|---|
| MD5/SHA256 Hash | Deterministic, no coordination | Collisions, long strings, slow |
| Auto-increment ID | Simple, guaranteed unique | Predictable, centralized bottleneck |
| Pre-generated ID Service | Fast lookup, no collisions | Requires separate service, storage |
| Snowflake ID | Distributed, time-ordered | Longer IDs, requires coordination |
| Counter-based (Base62) | Compact, unique, fast | Requires persistent counter |
Database Schema
CREATE TABLE urls (
id BIGINT PRIMARY KEY,
short_code VARCHAR(10) UNIQUE NOT NULL,
long_url TEXT NOT NULL,
user_id BIGINT,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
click_count BIGINT DEFAULT 0
);
CREATE INDEX idx_short_code ON urls(short_code);
CREATE INDEX idx_expires_at ON urls(expires_at);
Caching Strategy
Given the read-heavy workload, caching is essential:
- Cache-aside pattern: Check cache first, fall back to DB, populate cache on miss
- LRU eviction: Evict least recently accessed URLs
- TTL matching: Cache entries expire when URLs expire
- Cache hit ratio target: > 99% for hot URLs
Collision Handling
When generating short codes, collisions must be handled gracefully:
- Retry with different salt: Append timestamp or random bits and re-hash
- Check before insert: Verify uniqueness before writing to database
- Use longer codes: Increase length if collision rate is too high
- Database constraint: Let the unique constraint catch collisions and retry
Scaling Considerations
Read Path Optimization
The redirect path must be extremely fast:
- Client requests short URL
- Load balancer routes to nearest app server
- App server checks Redis cache
- Cache hit â return long URL immediately (sub-millisecond)
- Cache miss â query database, populate cache, return
Write Path Optimization
Creating new URLs is less latency-sensitive:
- Client submits long URL
- App server requests ID from ID Generator service
- App server encodes ID to Base62 short code
- Write to database (can be async)
- Populate cache proactively
- Return short URL to client
Database Sharding
Partition the URL table by short_code hash:
Practice Exercises
-
Design: How would you implement custom aliases (user-chosen short codes)? What additional constraints and validation are needed?
-
Scale: If the system handles 1B short URLs and 100K QPS reads, estimate the Redis cache memory needed assuming 500 bytes per entry and 99% cache hit ratio.
-
Trade-offs: Compare using a Snowflake ID vs a pre-generated ID pool for the URL shortener. What are the latency and complexity trade-offs?
-
Edge Case: How would you handle a viral URL that suddenly receives 10M requests in one minute? Design a strategy to prevent cache stampede.
What to Learn Next
-> Consistent Hashing Sharding data across nodes with minimal redistribution on scaling.
-> Caching Strategies Cache-aside, write-through, write-behind, and cache invalidation.
-> Databases SQL vs NoSQL, indexing, replication, and sharding strategies.
-> Design Unique ID Generator Snowflake IDs, UUIDs, and distributed ID generation strategies.
-> CDNs Caching static content at the edge for low-latency global access.
-> API Design REST conventions, versioning, and error handling patterns.