System Design for Beginners: How to Design a URL Shortener in 2026
Source: Dev.to
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 often asked to design systems such as:
- 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 ComponentsLet’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 – up 99.9 % of the time.
- Low latency – redirects in
Takeaway: The system is read‑heavy, needs aggressive caching, and has moderate storage requirements.
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 longUrlDELETE /api/v1/urls/{shortCode}
Response: 200 OK301 vs 302 redirect
- 301 (Permanent) – browsers cache the redirect → fewer requests to our servers → cheaper.
- 302 (Temporary) – browsers always hit our server → more accurate analytics.
Choose based on the product’s needs.
Step 4: Design the Data Model
Table schema (SQL example)
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 users
click_count BIGINT DEFAULT 0
);
CREATE INDEX idx_short_code ON urls(short_code);The short_code is the primary key. Below are three common generation strategies.
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] # first 7 chars of MD5Problem: hash collisions (different URLs → same short code).
Option 2 – Base62 Encoding of a Counter
BASE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def encode_base62(num: int) -> str:
if num == 0:
return BASE62[0] * 7
result = []
while num > 0:
result.append(BASE62[num % 62])
num //= 62
return ''.join(reversed(result)).zfill(7)How it works: Atomically increment a counter in the DB, then encode. Guarantees uniqueness. ✅
Option 3 – Pre‑generated Keys (Key‑Generation Service)
- Pre‑populate a
keys_availabletable with millions of unique 7‑char strings. - When a short URL is needed, fetch a key, move it 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 a long URL.
- Write service generates a short code (Base62 counter or KGS).
- Saves the mapping to the primary DB.
- Returns the short URL to the client.
Read Path
- Client GETs
/{shortCode}. - Read service looks up
Redisfirst (≈ 99 % cache hit). - On miss, query the read‑replica DB.
- Issue a 301/302 redirect.
Step 6: Deep Dive – Caching Strategy
With ~116 K reads/sec the database alone can’t keep up; 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:
# 1️⃣ Try cache first
cached = cache.get(f"url:{short_code}")
if cached:
return cached.decode()
# 2️⃣ Cache miss → query DB
long_url = db.query(
"SELECT long_url FROM urls WHERE short_code = %s", short_code
)
# 3️⃣ Populate cache for future hits
if long_url:
cache.setex(f"url:{short_code}", CACHE_TTL, long_url)
return long_urlCache eviction policy: LRU (Least Recently Used). By the 80/20 rule, ~20 % of URLs receive ~80 % of traffic – cache that hot 20 %.
Common Follow‑Up Questions
Q: How do you handle expired URLs?
- Lazy deletion: On read, check
expires_at; if past, return 410 Gone. - Background job: Periodically scan the table and delete expired rows (or move them to an archive).
Q: How would you support custom aliases?
- Validate the requested alias for uniqueness and profanity.
- Store it as
short_codedirectly; if a conflict occurs, return an error.
Q: How can you make the system globally distributed?
- Deploy read replicas and Redis caches in multiple regions.
- Use a geo‑DNS or anycast load balancer to route users to the nearest region.
- Employ a consistent‑hashing scheme for key generation to avoid collisions across data‑centers.
Q: How would you collect analytics (click counts, referrers, etc.)?
- Increment a
click_countcolumn asynchronously via a message queue (e.g., Kafka). - Store detailed events in a time‑series DB or data warehouse for later analysis.
Q: What if the short‑code space (7 chars, Base62) runs out?
- Switch to 8‑character codes (62⁸ ≈ 2.18 × 10¹⁴ possibilities).
- Or recycle unused/expired codes after a safe grace period.
TL;DR
- Clarify functional & non‑functional requirements.
- Estimate traffic, storage, and latency needs.
- Define a clean, versioned API.
- Model the data (URL ↔ short code) and decide on a generation strategy.
- Sketch a high‑level architecture with separate write/read services, a primary DB, read replicas, and a Redis cache.
- Dive into critical components (caching, expiration handling, analytics, global distribution).
Follow this framework and you’ll be able to tackle any system‑design interview – starting with the classic URL shortener. 🚀
ed 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 1 B 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 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.