Design a URL Shortener | System Design Question
Time to put the framework to work on a real problem. The URL shortener - TinyURL, Bitly - is the classic first system design question because it is small enough to finish in an hour yet exercises every step. We will walk it end to end, starting the way a real interview does: with the questions you ask and the answers you would get back, before a single box is drawn.
Step 1: Ask questions, and be ready for messy answers
The prompt "design a URL shortener" is vague on purpose, and here is the part the polished write-ups skip: you will not always get clean answers. A strong interviewer hands you numbers; a distracted or inexperienced one says "you decide," gives a fuzzy reply, or bolts on a surprise feature halfway through. Your job is to ask anyway and keep the problem moving no matter what comes back. A realistic exchange, punts and all:
You: Quick example - what does the service actually do? Interviewer: A long URL comes in, you return a short alias, and clicking the alias redirects to the original.
You: Roughly what scale - creates per day, or per second? Interviewer: Eh, you tell me.
That "you tell me" is where people freeze. Don't - propose a number, say it out loud, and write it down: "I'll assume 100 million new URLs a day, about 1,000 writes per second, and design for that - stop me if you had a very different scale in mind." Now you own the scale instead of waiting for it. Scale is the one thing you cannot leave unset: "5 GB once a day" and "5 PB per second" are completely different systems.
You: How short should the code be, and which characters can it use? Interviewer: As short as possible. Alphanumeric is fine.
Alphanumeric means 62 characters (base-62) - and it quietly rules out base64, whose + and / are not URL-safe. "As short as possible" you will turn into a concrete length during the estimate.
You: If two people shorten the same long URL, same code or different? Interviewer: Same code - dedupe them.
That one decision shapes your write path: dedupe means a lookup on every create. Had they said "different every time," you would skip that lookup entirely.
You: Anything beyond shorten and redirect - custom aliases, expiry, analytics? Interviewer: Actually, yeah - let's support custom aliases and click counts.
There is the curveball. A less experienced interviewer will cheerfully pile on scope, so keep the one-line cost of each ready: a custom alias adds a uniqueness check on a user-supplied code; click analytics adds an async events pipeline and nudges you toward a 302 redirect (which routes every click through your service) over a 301. You do not design them now - you name the cost and park them: "Got it - alias needs a collision check on user input, analytics needs an events pipeline; I'll get the core working first and fold those in if we have time."
The rule underneath all of it: when the interviewer answers, use it; when they punt, make a reasonable assumption and state it; when they add scope, name its cost and keep going. Never stall waiting for a tidy answer that may not come.
With that settled, write the scope as precise points - not prose:
Functional requirements
- Create a short URL from a given long URL.
- Redirect a short URL to the original long URL.
- Short codes are alphanumeric:
[0-9, a-z, A-Z]. - The same long URL maps to one short code (dedupe on create).
- Links are immutable - no edit, no delete.
- Custom aliases and click analytics: acknowledged, deferred.
Non-functional requirements
- Read-heavy: redirects outnumber creates roughly 10:1.
- Low redirect latency (target under ~100 ms).
- High availability - the redirect path is critical (~99.9%+).
- Short codes are unique and not trivially guessable in bulk.
- Storage scales to hundreds of billions of records.
A quick estimate turns the scale into numbers. From the 100 million a day you assumed:
Write QPS = 100,000,000 / 100,000 (seconds/day) = ~1,000/s
Read QPS = 1,000 x 10 = ~10,000/s
Records in 10 years = 100M x 365 x 10 = ~365 billion
Two facts fall out immediately and shape everything: the system is heavily read-heavy (so cache the redirect path), and it must store hundreds of billions of records (so the store has to scale). Write throughput at ~1,000/s is modest.
Step 2: High-level design
The API is just two REST endpoints:
POST /api/v1/data/shorten body: { longUrl } -> returns shortUrl
GET /{shortUrl} -> HTTP redirect to longUrl
The architecture is a stateless web tier behind a load balancer, a database for the mappings, a cache in front of reads, and a unique-ID generator that the shortening path uses (more on that in the deep dive).
The data model is trivial - a single table of mappings, so a relational database is a fine starting point:
url ( id BIGINT PRIMARY KEY, short_url VARCHAR, long_url VARCHAR )
301 vs 302 redirect is the one API subtlety worth raising. A 301 (permanent) tells the browser to cache the redirect, so future clicks skip your service entirely and go straight to the long URL - great for reducing server load. A 302 (temporary) routes every click through your service first - worse for load, but it lets you track click analytics. Pick 301 to offload traffic, 302 if analytics matter.
Step 3: Deep dive - generating the short code
This is the heart of the problem and where the interviewer wants depth: how do you turn a long URL into a unique 7-character code?
Why 7 characters? With 62 possible characters per position, a code of length n gives 62^n combinations. We need to cover 365 billion URLs, and 62^7 ≈ 3.5 trillion, comfortably more than enough - so 7 characters it is.
There are two standard approaches.
Option A - hash + collision resolution. Hash the long URL (CRC32, MD5, SHA-1) and take the first 7 characters. Simple, but two different URLs can collide on the same 7 chars. You resolve collisions by appending a predefined string and re-hashing until the code is free - which means a database check on every shorten, and a Bloom filter to make "does this code exist?" cheap.
Option B - base-62 conversion (the cleaner choice). Generate a unique numeric ID, then convert that number to base 62. Because each ID is unique, the resulting code is unique by construction - no collisions, no lookups to check.
The catch base-62 pushes onto you: you now need globally unique IDs in a distributed system. That is its own well-known sub-problem (a Snowflake-style distributed ID generator, or a database auto-increment for smaller scale) - worth naming so the interviewer knows you see it.
The shortening (write) flow ties it together, with a dedup check so the same long URL is not stored twice:
The redirect (read) flow is where the read-heavy requirement pays off. Because redirects dominate 10:1, we put a cache in front of the database: check the cache first, fall back to the database on a miss, and populate the cache for next time. Most redirects never touch the database at all.
Step 4: Wrap up
With the core designed, name the improvements you would make with more time - this is where you show you see past the happy path:
- Rate limiting. A malicious client could flood the shorten endpoint; throttle by IP or API key.
- Scale the web tier. It is stateless, so scaling is just adding servers behind the load balancer.
- Scale the database. ~365 billion rows means replication for read capacity and sharding (by the short code) for storage.
- Analytics. Click counts, referrers, and geography are a common follow-on - and the reason you might choose 302 over 301.
- Availability and consistency. Since a stale-but-available redirect is fine, this system leans toward availability; the mapping is immutable once written, which makes caching and replication easy.
Follow-up questions to expect
Once the core is on the board, the interviewer starts probing - this is where a lot of the signal is. Have crisp, reasoned answers ready:
- "What if the unique ID generator dies?" It is the one component the write path cannot do without, so I would not run it as a single node. A Snowflake-style generator gives each node its own machine id, so several run independently with no coordination, and I could hand each app server a pre-allocated id range as a fallback. Redirects never touch it, so reads stay up regardless.
- "A link goes viral - millions of hits a minute. What breaks?" Nothing new: that code is already hot in the cache, so it serves from memory. I would add cache replicas for headroom and, because the mapping is immutable, push it to a CDN or edge cache so the redirect resolves near the user without ever reaching our servers.
- "How would custom aliases work?" The user supplies the code, so I need a uniqueness check before saving - a conditional write against the same key space as generated codes - and I reject it and ask for another if it is already taken.
- "How do links expire?" Add an
expires_atfield, set a matching TTL on the cache entry, and let a background job sweep expired rows; a redirect past expiry returns410 Gone. - "Are the short codes guessable?" With sequential ids, base-62 codes are enumerable - someone could walk the whole space and scrape every link. If that matters, I would randomize or hash the id before encoding, trading a character or two of length for unpredictability.
- "Cache and database consistency?" This design gets lucky here: a mapping never changes once written, so the cache cannot go stale - there is no invalidation problem, only a cold-cache miss that loads the value once.
- "How do you scale this to 10x?" Almost nothing structural changes, which is the whole point: the stateless web tier adds instances, the cache adds replicas, the database shards further by short code, and the ID generator adds nodes.
What you just built
A URL shortener is the ideal first walkthrough because the shape generalizes: scope and estimate, sketch a stateless tier with a cache and a database, then deep-dive the one hard part - here, unique short-code generation via base-62 over a distributed ID. Reads dominate, so caching carries the load; writes are simple; storage is the thing that has to scale.
Next in the series we take on a read-heavy problem where the deep dive is harder - a news feed - and the fanout question from earlier posts comes back in full. The framework followed here is from Alex Xu's System Design Interview.

