· SWE Editorial · System Design · 7 min read
Design a URL Shortener: System Design Interview Guide
A complete walkthrough of the URL shortener interview question — requirements gathering, architecture, hash generation strategies, and read-heavy scaling patterns interviewers actually look for.
Why This Question Never Retires
“Design a URL shortener” (think TinyURL or bit.ly) has been a system design interview staple since the early 2010s, and it is still asked in 2026 at every FAANG-adjacent company and most well-funded startups. Interviewers keep coming back to it because it is small enough to finish in 45 minutes but rich enough to test requirements gathering, data modeling, hashing, caching, and scaling reasoning all in one sitting. If you can’t reason cleanly about this problem, you probably can’t reason cleanly about a more novel one either.
This guide walks through the problem the way a senior interviewer expects to see it handled: clarify scope, define requirements, sketch architecture, pick an encoding strategy, then defend it under follow-up pressure.
Step 1: Clarify Functional Requirements
Before drawing a single box, state what the system must do. A strong candidate narrates these out loud:
- Given a long URL, generate a unique short URL (e.g.,
sho.rt/aZ9kLp). - Given a short URL, redirect the user to the original long URL.
- Users can optionally pick a custom alias.
- Links can optionally expire after a set time (default: never, or a configurable TTL).
- Basic analytics: click count, referrer, timestamp (covered in depth in the companion article on data model and APIs).
Explicitly say what’s out of scope: no user authentication in v1, no link editing after creation, no malware/spam scanning (mention it as a “phase 2” concern to show maturity, but don’t design it now).
Step 2: Define Non-Functional Requirements
This is where junior candidates lose points — they jump to boxes and arrows without stating the constraints that justify those boxes.
- High availability — a broken redirect service breaks every link ever shared. Aim for 99.99%+.
- Low latency — redirects must feel instant, target sub-100ms at the app layer.
- Read-heavy workload — real-world ratios run roughly 100:1 to 1000:1 reads to writes. This single fact drives most of the architecture.
- Uniqueness — no two long URLs should collide into the same short code (or if they do, it must be handled deterministically).
- Scalability — must handle URL creation and redirection at growing volume without a rearchitecture (see the companion capacity estimation article for the numbers).
Say these five out loud in an interview. It signals you understand that architecture is a consequence of requirements, not a template you memorized.
Step 3: High-Level Architecture
At a high level, the system has four logical layers:
┌─────────────┐
│ Client │
└──────┬──────┘
│
┌──────▼──────┐
│Load Balancer│
└──────┬──────┘
│
┌────────────┴────────────┐
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Write Service│ │ Read Service│
│ (shorten API)│ │ (redirect) │
└──────┬──────┘ └──────┬──────┘
│ │
│ ┌──────▼──────┐
│ │ Cache │
│ │ (Redis/Memcached)
│ └──────┬──────┘
│ │
┌──────▼─────────────────────────▼──────┐
│ Database (Key-Value) │
│ short_code -> long_url mapping │
└─────────────────────────────────────────┘
The write path (URL creation) and the read path (redirection) are logically separate and should be reasoned about independently — this separation is the single most important architectural insight interviewers are listening for. The full request/response flow, including where the cache sits and how invalidation works, is covered in detail in the companion article “Architecture Diagram and Data Flow.”
Step 4: Hash Generation — The Core of the Problem
This is the part interviewers probe hardest. You need a strategy to turn a long URL into a short, unique code. Three common approaches, each with real tradeoffs:
Option A: Base62 Encoding of an Auto-Increment ID
Use a counter (from a database sequence or a distributed ID generator like Snowflake) and encode it in base62 ([a-zA-Z0-9], 62 characters).
counter = 125_000_000
base62(counter) = "8M0kX"
Pros: guaranteed uniqueness, no collision handling needed, short codes stay compact (7 base62 characters cover ~3.5 trillion values). Cons: requires a centralized or well-partitioned counter — a naive single global counter becomes a bottleneck and a single point of failure at scale. Sequential IDs can also leak information (competitors can estimate your traffic volume from code density) and are somewhat guessable.
Mitigation: partition counters per shard/worker (each app server owns a range of IDs, like a Twitter Snowflake pattern) and optionally shuffle/XOR the bits before encoding to de-sequentialize the visible short code.
Option B: Hash the Long URL (MD5/SHA-256), Take a Prefix
md5("https://example.com/very/long/path") = "9e107d9d372bb6826bd81d3542a419d6"
short_code = first 7 hex characters = "9e107d9"
Pros: stateless — no counter coordination needed, same input always produces the same code (naturally deduplicates repeated submissions of the same long URL). Cons: truncating a hash to 7 characters introduces real collision probability (birthday paradox math matters here — with millions of URLs, a 7-character truncated hash will collide). You need a collision-resolution loop: on collision, append a salt/counter and rehash, then check the DB again.
Option C: Pre-Generated Random Key Pool
A background worker continuously generates random, verified-unique 7-character codes and stores them in a “available keys” table. The write path simply pops one off the pool.
Pros: removes hash computation and collision-checking from the hot write path entirely — the write request just does a fast pop-and-assign. Cons: requires an extra always-on service and a bit more operational complexity; a key-pool depletion event is now a real failure mode you must design against (alerting on pool size, pre-fetching in batches).
Comparison Table
| Approach | Uniqueness Guarantee | Latency | Operational Complexity | Predictability Risk |
|---|---|---|---|---|
| Base62(counter) | Guaranteed | Very low | Medium (counter sharding) | Medium (sequential leak) |
| Hash + truncate | Probabilistic, needs retry | Low-medium | Low | Low |
| Pre-generated pool | Guaranteed | Very low | High (extra service) | Low |
Most production systems (and most “correct” interview answers) land on Base62 encoding of a sharded/distributed counter, sometimes combined with a pre-generated pool for burst absorption. Say this, then explain why — that’s the signal interviewers are grading.
Step 5: Optimizing for Read-Heavy Traffic
Since reads outnumber writes by orders of magnitude, most of your engineering effort should go into the read path:
- Cache aggressively. Put a Redis/Memcached layer in front of the database for short_code → long_url lookups, and apply the 80/20 rule (a small fraction of links get the vast majority of clicks) — see the capacity estimation article for exact cache sizing math.
- Use a CDN or edge redirect layer for extremely popular links so redirects never hit your origin servers at all.
- Keep the database read-replica-friendly — the mapping table is nearly immutable after creation, which makes it an ideal candidate for read replicas and even eventual-consistency reads.
- Return HTTP 301 vs 302 deliberately. A 301 (permanent redirect) lets browsers cache the redirect client-side, reducing load further but making it harder to change the destination or track every click. A 302 (temporary) hits your server every time — better for analytics, worse for pure scale. State this tradeoff explicitly; it’s a favorite follow-up question.
Common Follow-Up Questions to Prepare For
- “What happens if two servers generate the same short code at the same time?” (Answer: DB unique constraint + retry, or partitioned counters that make it structurally impossible.)
- “How would you support custom aliases without breaking the counter scheme?” (Answer: separate namespace check before insert, same table, different code-generation path.)
- “How do you handle a link that goes viral overnight?” (Answer: cache + CDN absorb the read spike; write path is untouched since the link already exists.)
Wrapping Up
The URL shortener question rewards candidates who resist the urge to start drawing boxes immediately. State functional and non-functional requirements first, justify every architectural decision against the read-heavy constraint, and be ready to defend your hash-generation choice with real tradeoff language, not just “I’ll use a hash function.” That structured approach — requirements, then architecture, then a defended encoding strategy — is what separates a pass from a “leans no hire.”
For 50+ system design deep-dives like this one, see The 0-to-1 SWE Interview Playbook (Amazon: https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20). It walks through this exact question alongside rate limiters, news feeds, and chat systems using the same requirements-first framework.
Continue this series with “Architecture Diagram and Data Flow” for the detailed request lifecycle, and “Capacity Estimation” for the QPS and storage math that justifies every design choice made here.