System Design Interview: How to Design a URL Shortener Step by Step
By Preeti Verma
"Design a URL shortener" is one of the most common system design interview questions, and for good reason. It looks simple, but it lets an interviewer test requirements gathering, data modelling, ID generation, caching and scaling in thirty minutes.
This walkthrough follows the order you would use in the interview. If system design is new to you, start with our guide to HLD vs LLD and a simple way to start.
Step 1: Clarify the requirements
Never start drawing boxes. Ask first. Good questions:
Functional requirements
- Given a long URL, the service returns a short one.
- Opening the short URL redirects to the original.
- (Optional) custom aliases, link expiry, and click analytics.
Non-functional requirements
- Redirects must be fast and the service highly available, because broken links are very visible.
- Short links should be hard to guess if they are meant to be private, and must be unique.
- The system is read-heavy: far more redirects than link creations.
Say out loud which of the optional features you will include. Scope control is part of the answer.
Step 2: Estimate the scale (state your assumptions)
Numbers show you can reason about size. They are assumptions, so label them clearly. For example:
- Assume 100 million new URLs per month, which is roughly 40 writes per second on average.
- Assume reads are 100 times writes, so about 4,000 redirects per second on average, with peaks higher.
- Assume each record is around 500 bytes. Over five years, 6 billion records is roughly 3 terabytes.
You do not need exact figures. You need to show that this is a read-heavy system with modest write volume and storage that fits in a sharded database, so caching and horizontal scaling matter.
Step 3: Design the API
Keep it small:
POST /api/shortenwith{ "longUrl": "...", "customAlias": "...", "expiresAt": "..." }returns{ "shortUrl": "https://sho.rt/aZ3k9Q" }.GET /{shortCode}returns a redirect to the long URL.
Mention authentication and rate limiting on the create endpoint so the service cannot be abused to spam.
Step 4: The key decision, generating the short code
A short code of 7 characters using base62 (a-z, A-Z, 0-9) gives 62^7, which is about 3.5 trillion combinations. That is plenty. The interview question is how to produce unique codes. Compare options honestly:
Option A: Hash the long URL (for example, take the first 7 characters of an MD5 or SHA hash, encoded in base62).
- Simple, and the same URL gives the same code.
- But collisions are possible, so you must check the database and retry. Different users shortening the same URL may want different links.
Option B: A counter, encoded in base62.
- Take a unique incrementing number and convert it to base62. No collisions by construction.
- The catch: a single counter is a bottleneck and a single point of failure, and sequential codes are guessable.
Option C: A key generation service that hands out ranges.
- Each application server asks a coordinator for a block of IDs (for example 1 million at a time) and uses them locally. This removes the per-request bottleneck and avoids collisions.
- To avoid guessable codes, shuffle or encode the number with a reversible scheme.
Option D: Pre-generate random unused keys in a separate table and mark them used as you assign them.
A strong answer picks one, say Option C, and explains why: no collisions, no per-request coordination, and easy to scale. Then mention the downside you accepted (a server crash can waste part of its unused range, which is fine given how many codes exist).
Step 5: Choose the storage
The access pattern is simple: look up a long URL by short code, and insert new records. There are no complex joins. That suits a key-value or wide-column store (such as DynamoDB or Cassandra), which scales horizontally. A relational database (PostgreSQL or MySQL) also works well at moderate scale with the short code as the primary key, and sharding when it grows. Say which you would start with and why.
A simple table:
| Field | Notes | |---|---| | short_code | primary key | | long_url | the destination | | created_at | for analytics and cleanup | | expires_at | optional | | user_id | optional, for ownership |
Step 6: The redirect path
When a user opens https://sho.rt/aZ3k9Q:
- The request hits a load balancer, then an application server.
- The server checks a cache (Redis) for the code.
- On a hit, it returns the redirect immediately. On a miss, it reads from the database, stores the result in the cache, and returns it.
301 or 302? A 301 (permanent) redirect lets browsers cache the mapping, which lowers load but means you lose click counts and cannot change the target later. A 302 (temporary) sends every click through your server, which keeps analytics accurate and lets you expire links. Explain the trade-off and pick based on whether analytics matter.
Step 7: Caching
Because the system is read-heavy and popular links get most of the traffic, a cache gives a huge win. Use an LRU eviction policy, size the cache for the hot set (a small fraction of all links), and set a TTL. Mention cache stampede on a suddenly viral link and how you would avoid it, for example by letting only one request refill the cache entry.
Step 8: Scaling and reliability
- Application servers are stateless, so scale them horizontally behind a load balancer.
- Database: shard by short code (hashing it spreads load evenly), and replicate for availability and read capacity.
- Cache: use a clustered Redis with replicas.
- Multiple regions: serve redirects from the region closest to the user, and consider a CDN or edge layer for popular links.
Step 9: Extra features, briefly
- Custom aliases: check availability, reject reserved words, and make the uniqueness check atomic (a unique constraint).
- Expiry: store
expires_at, check it on read, and clean up with a background job. - Analytics: do not write click data in the redirect path. Send events to a queue (such as Kafka) and aggregate asynchronously, so redirects stay fast. See our Kafka interview questions for how that works.
- Abuse and safety: rate limit creation, scan destinations for malware or phishing if you can, and allow reporting of bad links.
Common mistakes in this interview
- Jumping to the database before clarifying requirements.
- Generating codes with a hash and ignoring collisions.
- Forgetting that the system is read-heavy and not mentioning a cache.
- Putting analytics writes inside the redirect request.
- Not discussing a single point of failure in the ID generator.
How to practise
Draw this design on paper and explain it out loud in 30 minutes, including at least two trade-offs. Then try another classic, such as a rate limiter or a notification system, using the same steps: requirements, scale, API, data, the one hard problem, scaling. A mock interview with system design practice lets you explain your design and get follow-up questions. You can also see the bigger picture in our 30-day preparation plan.
FAQ
Do I need exact numbers? No. Reasonable, clearly labelled assumptions are what matter, along with showing how the numbers shape the design.
Which database should I choose? Either works if you justify it. The access pattern is a key lookup, which favours key-value stores at very large scale, and a relational database is perfectly fine to start with.
How long should my answer be? Aim for 35 to 45 minutes in a full round. Spend the most time on the code generation and caching trade-offs.
The takeaway
Clarify requirements, estimate scale, pick a unique code strategy and defend it, add a cache for the read-heavy path, and talk about how it scales and fails. Interviewers care less about the final diagram than about whether you can explain why each choice was made.