Design a URL shortener
Like you should in an interview. Explained as simply as possible… but not simpler.
In this issue, I walk through the exact thinking I’d use in a system design interview out loud, step by step. Clear, practical, and including trade-offs you can defend.
What you’ll learn in ~15 minutes
How I would scope the problem without missing important requirements (custom aliases, expirations, availability).
A cache-first read path that keeps redirect p95 under ~200 ms.
Short-code generation strategies.
When I would choose 302 over 301 and the implications for observability and cost.
How this issue is structured
I split the write-up into the same sections I’d narrate at a whiteboard. Free readers get the full walkthrough up to the deep-dive parts. Paid members get the 🔒 sections.
Initial Thoughts & Clarifying Questions
Functional Requirements
Non-Functional Requirements
🔒 Back-of-the-envelope Estimations (QPS, storage, bandwidth, cardinality math)
🔒 System Design (the architecture I’d draw and the excalidraw link for it!)
🔒 Component Breakdown (why each piece exists + alternatives)
🔒 Trade-offs Made
🔒 Security & Privacy (abuse, enumeration resistance, 301/302)
🔒 Monitoring, Logging, and Alerting
Quick note: If you’ve been getting value from these and want the full deep dives, becoming a paid member helps me keep writing—and you’ll immediately unlock the 🔒 sections above, plus a few extras I lean on when I practice.
Members also get
12 Back-of-the-Envelope Calculations Every Engineer Should Know
My Excalidraw System Design Template — drop-in canvas you can copy and tweak.
My System Design Component Library
Let’s get to it!
Initial Thoughts & Clarifying Questions
I’d start by tightening the scope. At minimum, users should be able to create a short link and later hit that short link to get redirected to the original URL.
Clarifying questions I’d ask (and I’ll state my assumptions so we can move on):
Who are the users? Anonymous web users. I’ll assume no login is required for basic shortening; a simple “user” concept exists if we later need ownership/analytics.
Custom aliases? Supported, unique if available; conflict returns an error.
Expirations? Optional per-link TTL; expired links should stop redirecting.
Latency target? ~200 ms for redirects (human-real-time).
Scale? Roughly 100M DAU and ~1B total shortened URLs over time.
Consistency vs availability? Optimize for high availability with eventual consistency; strong read-after-write is not required for brand-new links.
Redirect status? Prefer 302 (temporary) to keep requests flowing through our service for observability/analytics, vs 301 which may get cached across the internet. We’ll justify this later.
Functional Requirements
From what I understand, the core requirements are:
Create a short URL from a long URL. Optionally accept a custom alias and an expiration time. Return the full short URL.
POST /urls
Body:{ long_url, custom_alias?, expires_at? }
→ Returns:{ short_url }
Redirect: Hitting the short URL should perform an HTTP redirect to the original URL (respecting expiration).
GET /{shortCode}
→302 Location: long_url
(or error if expired/not found)
Non-Functional Requirements
I’d expect this system to be:
Low-latency on redirects: p95 under ~200 ms end-to-end.
Highly available: Favour availability over strict read-after-write consistency; eventual consistency is acceptable for newly created links.
Uniqueness: Short codes must be unique (no collisions), including for custom aliases.
Scalable: Support ~100M DAU and ~1B stored URLs with headroom for peak QPS.
Cost-conscious: Lean heavily on caching to offload reads; keep storage simple.
Operable & observable: Clear SLOs, metrics, logs, and tracing.
🔒 Back-of-the-envelope Estimations
I’ll do the math only where it informs decisions: