Requirements & scope
Problem Statement & Requirements
Functional Requirements
- Given a long URL, generate a short, unique alias (e.g.,
https://short.ly/abc123) - When a user hits the short URL, redirect them to the original long URL (HTTP 301/302)
- Users can optionally pick a custom short alias
- Links can have an expiration time (TTL)
- Analytics: track click count, referrer, geo, device (optional but expected in interviews)
Non-Functional Requirements
- High availability — redirections must never go down
- Low latency — redirect in < 10 ms (p99)
- URL should not be guessable — no sequential IDs
- Consistency — same long URL can map to multiple short URLs (user-specific), but a short URL must always resolve to exactly one long URL
- Durability — once created, a short link must survive infrastructure failures
Out of Scope
- User authentication/accounts (simplify for interview)
- Link editing after creation
- Bulk URL shortening API
Scale estimations
Scale Estimations
Traffic
| Metric | Value |
|---|---|
| New URLs created / day | 100M (write-heavy assumption) |
| Reads (redirections) / day | 10B (read:write = 100:1) |
| Reads / second | ~116K RPS |
| Writes / second | ~1,160 RPS |
| Peak reads / second | ~350K RPS (3x average) |
Storage
| Metric | Value |
|---|---|
| Average URL length | 500 bytes |
| Metadata per URL | 100 bytes (created_at, expires_at, user_id, click_count) |
| Short key | 7 bytes |
| Total per record | ~607 bytes → round to 1 KB |
| Records over 5 years | 100M/day × 365 × 5 = 182.5B records |
| Storage over 5 years | 182.5B × 1 KB = ~182.5 TB |
Bandwidth
| Metric | Value |
|---|---|
| Incoming (writes) | 1,160 × 1 KB = ~1.16 MB/s |
| Outgoing (reads) | 116K × 1 KB = ~116 MB/s |
Cache
- 80/20 rule: 20% of URLs generate 80% of traffic
- Cache 20% of daily read requests: 10B × 0.2 × 1 KB = ~2 TB cache
- Fits in a Redis cluster
Layered architecture
High-Level Architecture
High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
Component Breakdown
Component BreakdownExcalidraw diagram · editable shapes · reveal step by stepExplore
API & contracts
API Design
Create Short URL
POST /api/v1/urls
Content-Type: application/json
Request:
{
"long_url": "https://www.example.com/very/long/path?q=something",
"custom_alias": "my-brand", // optional
"expires_at": "2026-12-31T00:00:00Z" // optional
}
Response (201 Created):
{
"short_url": "https://short.ly/abc123",
"short_key": "abc123",
"long_url": "https://www.example.com/very/long/path?q=something",
"expires_at": "2026-12-31T00:00:00Z",
"created_at": "2026-04-03T10:00:00Z"
}Redirect (Read)
GET /:short_key
→ HTTP 301 (permanent) or 302 (temporary) redirect to long_url
301 vs 302 Tradeoff:
- 301: Browser caches the redirect → less load on our servers,
but we lose analytics visibility (browser skips us on repeat visits)
- 302: Browser always hits us → more load but full analytics tracking
→ Use 302 if analytics matter; 301 if pure performance is the goalDelete URL
DELETE /api/v1/urls/:short_key
→ 204 No ContentData model
Data Model
URL Table (Primary)
URL Table (Primary)Excalidraw diagram · editable shapes · reveal step by stepExplore
Database Choice
| Option | Pros | Cons |
|---|---|---|
| DynamoDB | Managed, auto-scaling, single-digit ms latency, partition by short_key | No joins (not needed here), cost at extreme scale |
| Cassandra | Linear scalability, tunable consistency, great for write-heavy | Operational complexity, eventual consistency |
| MySQL + sharding | ACID, familiar, strong consistency | Manual sharding, operational burden at scale |
Recommendation: DynamoDB or Cassandra — this is a key-value lookup workload with high write throughput. No joins, no complex queries.
Core design decisions
Key Generation — The Core Design Decision
This is the most important part of the design. How do we generate a short, unique, non-guessable key?
Option 1: Hash the Long URL (MD5/SHA-256 + Base62 Truncation)
Option 1: Hash the Long URL (MD5/SHA-256 + Base62 Truncation)Excalidraw diagram · editable shapes · reveal step by stepExplore
Key space: Base62 with 7 characters = 62^7 = 3.5 trillion unique keys
Collision handling:
Option 1: Hash the Long URL (MD5/SHA-256 + Base62 Truncation)Excalidraw diagram · editable shapes · reveal step by stepExplore
| Pros | Cons |
|---|---|
| Deterministic — same URL → same hash | Collisions require DB lookup + retry |
| Simple to implement | Same long URL → same short URL (no per-user links) |
| No coordination needed | MD5 is not cryptographically secure (fine for this use) |
Option 2: Pre-Generated Key Pool (Key Generation Service — KGS)
Option 2: Pre-Generated Key Pool (Key Generation Service — KGS)Excalidraw diagram · editable shapes · reveal step by stepExplore
How it works:
- Offline job generates random 7-char Base62 keys, stores in
unused_keystable - App server requests a batch (e.g., 1,000 keys) at startup, caches them in memory
- On URL creation, pop a key from the in-memory batch
- When batch runs low (< 200), fetch another batch asynchronously
- Mark keys as "used" in the DB atomically
Concurrency safety:
- Use a two-table approach:
unused_keysandused_keys - Move key from unused → used in a single transaction
- If an app server crashes, its unused in-memory keys are "lost" — acceptable given the 3.5T key space
| Pros | Cons |
|---|---|
| Zero collision — guaranteed unique | Need a separate KGS service |
| O(1) key generation — no DB check needed | Pre-generated keys take storage |
| Fast — just pop from in-memory cache | Wasted keys if server crashes (negligible) |
| No hash computation | Single point of failure → need standby KGS |
Option 3: Counter-Based with Encoding (Snowflake-style)
Option 3: Counter-Based with Encoding (Snowflake-style)Excalidraw diagram · editable shapes · reveal step by stepExplore
| Pros | Cons |
|---|---|
| Zero collision — ranges don't overlap | Sequential/predictable (guessable) |
| No DB lookup for uniqueness check | Requires Zookeeper or similar coordination |
| Simple, efficient | Range exhaustion needs re-allocation |
Recommendation
Use Option 2 (KGS) for production systems:
- No collisions, no coordination overhead per request
- O(1) amortized key generation
- Easy to make non-guessable (random keys, not sequential)
- Can be made highly available with standby KGS replicas
Request flows
Detailed Flow Diagrams
Write Flow (URL Creation)
Write Flow (URL Creation)Excalidraw diagram · editable shapes · reveal step by stepExplore
Read Flow (Redirection)
Read Flow (Redirection)Excalidraw diagram · editable shapes · reveal step by stepExplore
Analytics Flow (Async)
Analytics Flow (Async)Excalidraw diagram · editable shapes · reveal step by stepExplore
Performance & caching
Caching Strategy
Cache Architecture
Cache ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore
Caching Patterns
| Pattern | How It Works | When to Use |
|---|---|---|
| Cache-Aside | App checks cache → miss → query DB → populate cache | ✅ Best for this use case |
| Write-Through | Write to cache + DB on creation | Wastes cache on URLs that may never be read |
| Write-Behind | Write to cache, async flush to DB | Risky — URL could be lost if cache crashes |
Recommendation: Cache-Aside with Write-on-Create for popular URLs
- On URL creation: don't populate cache (most URLs are rarely accessed)
- On first read: query DB, populate cache
- Exception: if we detect a URL going viral (high click rate), proactively cache it
Cache Warming
- Preload the top 1% most-accessed URLs into cache on service startup
- Use a daily batch job scanning analytics to identify hot URLs
Advanced design
Database Sharding & Partitioning
Sharding Strategy
Sharding StrategyExcalidraw diagram · editable shapes · reveal step by stepExplore
Recommendation: Consistent Hashing on short_key
- Even distribution
- Adding/removing DB nodes only requires moving ~1/N of the keys
- Use virtual nodes (vnodes) for better balance — 150-200 vnodes per physical node
Replication
ReplicationExcalidraw diagram · editable shapes · reveal step by stepExplore
Edge cases
Handling Edge Cases
Duplicate Long URLs
- Same user, same URL: Return existing short URL (dedup query on
long_url + user_idindex) - Different users, same URL: Generate separate short URLs (user isolation)
Expired URLs
- Lazy deletion: On read, check
expires_at→ if expired, return 410 Gone - Background cleanup: Cron job deletes expired records, recycles keys back to KGS
Custom Aliases
- Validate: 3-16 chars, alphanumeric + hyphens only
- Check uniqueness in DB
- Reserve a set of blacklisted words (profanity, reserved paths like
/api,/admin)
Hot URLs (Viral Links)
- A single URL getting millions of hits/second
- Solution: Cache replication — replicate hot keys across multiple cache shards
- Application-level: detect hot keys (e.g., > 1000 RPS) and fan out reads across replicas
Tradeoffs
Tradeoffs & Design Decisions Summary
| Decision | Option A | Option B | Chosen | Why |
|---|---|---|---|---|
| Key Generation | Hash + collision check | Pre-generated KGS | KGS | Zero collisions, O(1), non-guessable |
| Redirect Code | 301 (permanent) | 302 (temporary) | 302 | Preserves analytics; 301 loses repeat visit tracking |
| Database | SQL (MySQL/Postgres) | NoSQL (DynamoDB/Cassandra) | NoSQL | Key-value access pattern, high write throughput, linear scaling |
| Caching | Write-through | Cache-aside | Cache-aside | Most URLs are rarely accessed; avoid wasting cache on cold URLs |
| Sharding | Range-based | Consistent hashing | Consistent hashing | Even distribution, graceful scaling |
| ID Length | 6 chars (56B keys) | 7 chars (3.5T keys) | 7 chars | Room for 5+ years at 100M URLs/day |
| Analytics | Synchronous in request path | Async via Kafka | Async Kafka | Don't add latency to the redirect hot path |
Reliability & fault tolerance
Reliability & Fault Tolerance
Single Points of Failure & Mitigations
Single Points of Failure & MitigationsExcalidraw diagram · editable shapes · reveal step by stepExplore
Graceful Degradation
- If cache is down → serve directly from DB (higher latency, still functional)
- If KGS is down → app servers use remaining in-memory keys; alert for manual intervention
- If analytics pipeline is down → buffer events locally, replay later
Production architecture
Full System Architecture (Production-Grade)
Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
Further exploration
Workshop note · added for the website’s common reading format
Rebuild URL Shortener from memory, then change one assumption: ten times more traffic, a new region, or a stricter consistency requirement. Which component must change first—and which does not?
Compare URL service with the same boundary in a related design. Write down one alternative you rejected, what it would simplify, and when you would choose it instead. Follow the source link at the end of this article to explore the original document.
Interview playbook
Interview Tips
Start with requirements clarification — Don't jump into design. Ask about scale, custom aliases, analytics, expiration.
Lead with the key generation discussion — This is the core algorithmic challenge. Discuss all three approaches, tradeoffs, and recommend KGS.
Separate read and write paths — They have very different scale (100:1 ratio). This shows you think about system characteristics.
Don't forget caching — With 100:1 read:write ratio, caching is critical. Discuss eviction, warming, and hot key handling.
Mention 301 vs 302 — This is a subtle but important tradeoff that shows depth.
Discuss cleanup of expired URLs — Shows you think about long-term system health and operational concerns.
End with reliability — Walk through failure scenarios. "What happens if X goes down?" for each component.