System Design for Beginners: How to Design a URL Shortener in 2026
System design interviews terrify most developers. The questions are open-ended, the scope is massive, and there's no single "right" answer.
But here's the secret: every system design question follows the same framework. Once you learn it with a simple example, you can apply it to anything.
We'll use the URL shortener — the most classic system design question — as our learning vehicle.
What Is System Design?
System design is the process of defining the architecture, components, modules, interfaces, and data flow for a system to satisfy given requirements.
In interviews, you're asked to design systems like:
- URL shortener (bit.ly)
- Twitter/X timeline
- YouTube
- Uber's ride dispatch
- Netflix video streaming
The System Design Framework (6 Steps)
1. Clarify Requirements
2. Estimate Scale
3. Define the API
4. Design the Data Model
5. Draw the High-Level Architecture
6. Deep Dive into Components
Let's apply each step to the URL shortener.
Step 1: Clarify Requirements
Functional requirements (what the system does):
- Given a long URL, create a short URL (e.g.,
bit.ly/abc123) - Given a short URL, redirect to the original long URL
- Short URLs should expire after a configurable time (optional)
- Users can create custom short URLs (optional)
Non-functional requirements (how well it does it):
- High availability: the system should be up 99.9% of the time
- Low latency: redirects should happen in < 100ms
- Durability: short URLs shouldn't disappear unexpectedly
Step 2: Estimate Scale
Asking about scale shows you think like a senior engineer:
Assumptions:
- 100M new URLs shortened per day
- Read/write ratio: 100:1 (100 redirects per 1 new URL)
- Average URL size: 100 bytes
Calculations:
- Write QPS: 100M / 86,400 sec ≈ 1,160 writes/sec
- Read QPS: 1,160 × 100 = 116,000 reads/sec
- Storage (5 years): 100M × 365 × 5 × 100 bytes ≈ 18 TB
This tells us: read-heavy system, needs caching, moderate storage needs.
Step 3: Define the API
POST /api/v1/urls
Request: { "longUrl": "https://example.com/very/long/path", "expiresAt": "2027-01-01" }
Response: { "shortUrl": "https://bit.ly/abc123", "expiresAt": "2027-01-01" }
GET /{shortCode}
Response: 301 Redirect to longUrl
DELETE /api/v1/urls/{shortCode}
Response: 200 OK
301 vs 302 redirect:
-
301(Permanent): browser caches the redirect → fewer requests to our servers → cheaper -
302(Temporary): browser always asks our server → more accurate analytics
Choice depends on requirements.
Step 4: Design the Data Model
Simple schema:
CREATE TABLE urls (
short_code VARCHAR(7) PRIMARY KEY,
long_url VARCHAR(2048) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
user_id BIGINT, -- nullable for anonymous
click_count BIGINT DEFAULT 0
);
CREATE INDEX idx_short_code ON urls(short_code);
The short_code is the key. How do we generate it?
Short Code Generation Strategies
Option 1: Hash + Truncate
import hashlib
def generate_short_code(long_url: str) -> str:
hash_value = hashlib.md5(long_url.encode()).hexdigest()
return hash_value[:7] # Take first 7 chars of MD5 hash
Problem: hash collisions (two URLs → same short code).
Option 2: Base62 Encoding of a Counter
BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def encode_base62(num: int) -> str:
result = []
while num > 0:
result.append(BASE62[num % 62])
num //= 62
return ''.join(reversed(result)).zfill(7)
Atomically increment a counter in the DB, encode it. Unique by design. ✅
Option 3: Pre-generated Keys (Key Generation Service)
Pre-generate millions of unique 7-char strings, store in a keys_available table. When needed, grab one and move to keys_used. Eliminates collision risk entirely.
Step 5: High-Level Architecture
[Client]
│
▼
[Load Balancer]
│
├─── [Write Service] ──── [Primary DB]
│ │
└─── [Read Service] ──── [Redis Cache] ──── [Read Replica DB]
Write path:
- Client POSTs long URL
- Write service generates short code (Base62 counter or KGS)
- Saves to primary DB
- Returns short URL
Read path:
- Client GETs short code
- Read service checks Redis cache first (99% of requests should hit cache)
- Cache miss → query read replica DB
- Return 301/302 redirect
Step 6: Deep Dive — Caching Strategy
With 116K reads/sec, the database alone can't handle traffic. Redis is essential.
import redis
cache = redis.Redis(host='localhost', port=6379)
CACHE_TTL = 3600 # 1 hour
def get_long_url(short_code: str) -> str | None:
# Try cache first
cached = cache.get(f"url:{short_code}")
if cached:
return cached.decode()
# Cache miss — query database
long_url = db.query("SELECT long_url FROM urls WHERE short_code = %s", short_code)
if long_url:
cache.setex(f"url:{short_code}", CACHE_TTL, long_url)
return long_url
Cache eviction policy: Use LRU (Least Recently Used). 80/20 rule — 20% of URLs get 80% of traffic. Cache that 20%.
Common Follow-Up Questions
Q: How do you handle expired URLs?
- Lazy deletion: check expiry on read, return 410 Gone if expired
- Background job: scan and delete expired URLs nightly
Q: How do you prevent abuse?
- Rate limiting per IP (Redis sliding window counter)
- Block malicious URLs (Safe Browsing API)
- Captcha for anonymous users
Q: How do you scale to 1B requests/day?
- Add more read replicas
- Geographic distribution (CDN for redirects)
- Consistent hashing for distributed cache
Q: Custom short codes?
- Check uniqueness before saving
- Separate table for reserved/custom codes
What Interviewers Are Actually Evaluating
- Communication — Can you walk through your thinking clearly?
- Trade-offs — Do you understand why each decision was made?
- Scale awareness — Do you estimate before building?
- Depth — Can you go deep on at least one component when asked?
You don't need to have the perfect answer. You need to drive the conversation, make reasonable choices, and explain your reasoning.
Practice Problems (Increasing Difficulty)
- URL Shortener (this article) — Start here
- Pastebin (text sharing)
- Rate Limiter (classic API design)
- Twitter Timeline (fan-out problem)
- Design YouTube (video storage + streaming)
- Design Uber (real-time matching)
Managing freelance projects and clients while studying system design? Freelancer OS keeps everything organized — CRM, projects, income, all in Notion. €19 one-time.
Top comments (0)