A URL shortener system design interview answer describes a service, like TinyURL or Bitly, that maps long URLs to short unique codes and redirects each code back to its original URL with very low latency. The standard interview answer pairs a counter-based ID generator with base62 encoding, stores mappings in a horizontally scalable key-value store, and puts a cache in front of reads, because traffic is roughly 100 reads for every write. The rest of the interview is about proving you can scale that core to billions of links and defend each trade-off.
This guide gives you the full answer as a 45-minute script, with real math, a schema, and a scorecard showing what interviewers reward.
Key Takeaways
- The workload is read-heavy. Assume around 100:1 reads to writes. Every major decision (cache, replicas, redirect type) follows from that ratio.
- Seven base62 characters is enough. 62^7 is about 3.52 trillion codes, far above the roughly 182.5 billion URLs a 100M-per-day service creates in five years.
- Counter plus base62 beats hashing for uniqueness. Hashing needs a collision check on every write. A range-allocated counter does not.
- 301 vs 302 is an analytics decision. Permanent redirects get cached by browsers and hide clicks from you. Temporary redirects keep every click visible.
- Strong hire lives in the deep dives. Range allocation, hot keys, async analytics, and abuse handling separate senior answers from correct-but-shallow ones.
Why Do Interviewers Still Ask the URL Shortener Question?
The URL shortener is a calibration question. The core design fits on one whiteboard, so the interviewer can spend most of the session watching how you scope, estimate, and go deep. It is modeled on public services like TinyURL and Bitly, and it appears in system design rounds at many companies, including those described in our Google technical interview process guide.
Interviewers have seen every memorized answer, so they score your reasoning. If you want the general framework first, read how to ace the system design interview, then come back for this specific walkthrough.
The 45-Minute Answer Script
Follow this timeline. Each section below gives the exact content to cover.
| Time | Phase | What you produce | Common mistake |
|---|---|---|---|
| 0:00 to 5:00 | Requirements | Functional and non-functional list, agreed scope | Asking 15 questions and never committing |
| 5:00 to 10:00 | Estimates | QPS, storage, code length, cache size | Skipping math or doing it to 4 significant figures |
| 10:00 to 15:00 | API | Create and redirect endpoints | Forgetting status codes and error cases |
| 15:00 to 20:00 | Data model | Table schema, database choice | Picking a database without a reason |
| 20:00 to 30:00 | High-level design | Boxes and arrows for write and read paths | Drawing a cache without saying what it caches |
| 30:00 to 42:00 | Deep dives | ID generation, caching, analytics, expiry | Waiting for the interviewer to pick the topic |
| 42:00 to 45:00 | Wrap-up | Bottlenecks, failure modes, what you would add | Ending abruptly with no summary |
Minutes 0 to 5: What Are the Requirements?
Requirements are a contract. Your goal is to agree on scope in five minutes, write it down, and move on.
Functional requirements:
- Given a long URL, return a unique short URL.
- Visiting the short URL redirects to the original.
- Users can optionally choose a custom alias (for example,
/spring-sale). - Links can have an optional expiration time.
- The system records click analytics (count, time, referrer, rough geography).
Non-functional requirements:
- Low redirect latency. Redirects should feel instant, so target a p99 well under 100 ms on the server side.
- High availability. A broken redirect breaks every link ever shared, so reads should favor availability.
- Uniqueness. Two different long URLs must never get the same code.
- Unpredictability (nice to have). Codes should not be trivially enumerable.
Say out loud which items are out of scope, such as user accounts, link editing, or a web dashboard. Interviewers score this as judgment. A good line is: "I will treat analytics as a secondary path that must never slow down redirects."
Minutes 5 to 10: Back-of-the-Envelope Estimates
Estimates exist to drive decisions, so round aggressively and tie each number to a choice. Use these assumptions unless the interviewer gives you others: 100 million new URLs per day, a 100:1 read-to-write ratio, five years of retention, and about 500 bytes per stored record.
| Metric | Calculation | Result | Decision it drives |
|---|---|---|---|
| Write QPS | 100M / 86,400 s | ~1,200/s avg, ~2,400/s peak | One write tier is easy, ID generation must not be a bottleneck |
| Read QPS | 1,200 x 100 | ~120,000/s avg | Cache is mandatory |
| Total URLs (5 years) | 100M x 365 x 5 | ~182.5 billion | 7-character codes |
| Storage | 182.5B x 500 B | ~91 TB | Must shard across nodes |
| Read bandwidth | 120,000 x 500 B | ~60 MB/s | Not a bottleneck |
| Cache (upper bound) | 20% of daily reads x 500 B | up to ~1 TB, far less after dedup | A Redis cluster, not one box |
The code length math is the part interviewers check most. Base62 uses a-z, A-Z, and 0-9, giving 62 symbols per position:
- 62^6 = about 56.8 billion codes, not enough for 182.5 billion URLs.
- 62^7 = about 3.52 trillion codes, roughly 19 times the five-year need.
So you pick 7 characters and move on.
Minutes 10 to 15: Designing the API
Keep the API small and precise. Two endpoints cover the core flow.
POST /api/v1/urls
Content-Type: application/json
{
"long_url": "https://example.com/some/very/long/path?utm_source=newsletter",
"custom_alias": "spring-sale",
"expires_at": "2026-12-31T23:59:59Z"
}
201 Created
{
"short_url": "https://sho.rt/spring-sale",
"code": "spring-sale",
"expires_at": "2026-12-31T23:59:59Z"
}
GET /{code}
302 Found
Location: https://example.com/some/very/long/path?utm_source=newsletter
Mention the error cases: 409 Conflict when a custom alias is taken, 404 Not Found for unknown codes, and 410 Gone for expired links. Add that the create endpoint needs an API key and rate limiting to stop abuse. If the interviewer bites, you can point to the token bucket approach covered in our rate limiter system design guide.
Should the Redirect Be 301 or 302?
This is a guaranteed follow-up, so answer it before you are asked. Per RFC 9110, a 301 means the resource has moved permanently, so browsers may cache it and send future clicks straight to the destination. A 302 is temporary, so every click comes back through your servers.
Pick 302 (or 307) when analytics, expiry, or abuse takedowns matter, which is almost always. Pick 301 only if the interviewer says load reduction matters more than tracking.
Minutes 15 to 20: Database Choice and Schema
The access pattern decides the database. Nearly every request is "given this code, return the long URL," a single-key lookup with no joins and no cross-row transactions.
CREATE TABLE urls (
code VARCHAR(16) PRIMARY KEY,
long_url TEXT NOT NULL,
user_id BIGINT,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
is_custom BOOLEAN NOT NULL DEFAULT FALSE
);
You have two defensible choices:
- Key-value or wide-column store (DynamoDB, Cassandra). Partition by
code, scale horizontally, and get high availability out of the box. This fits the 91 TB, read-heavy workload naturally. - Sharded relational database (PostgreSQL, MySQL). The primary key gives you a strict uniqueness guarantee, which is useful for custom aliases. You pay for it with manual sharding work.
Either is fine if you justify it. Say something like: "I will use a key-value store partitioned by code. The workload is point lookups with no joins, and I need to spread 91 TB across nodes." Keep click events in a separate store, since they are append-only and much higher volume than the mapping table.
Minutes 20 to 30: High-Level Design
Draw two paths and narrate each one.
Write path: Client calls the API gateway, which handles auth and rate limits. The URL service gets a new ID from its local ID range, encodes it in base62, writes code -> long_url to the database, and returns the short URL.
Read path: Client hits GET /{code}. The request lands on a stateless redirect service behind a load balancer. The service checks the cache, falls back to the database on a miss, fills the cache, checks expiry, emits a click event to a queue, and returns a 302.
The point to say out loud is that the redirect service is stateless, so you scale it by adding instances. The only stateful pieces are the database, the cache, and the ID allocator, and each one gets its own deep dive.
Minutes 30 to 42: ID Generation Deep Dive
ID generation is where most of the scoring happens. Compare three approaches, then commit.
| Approach | How it works | Pros | Cons |
|---|---|---|---|
| Hash and truncate | MD5 or SHA-256 of the long URL, take the first 7 base62 characters | Same URL can map to the same code, no coordination | Collisions need a check and retry on every write |
| Random code | Generate 7 random base62 characters | Unpredictable, simple | Same collision check, gets worse as the keyspace fills |
| Counter plus base62 | Unique integer ID encoded in base62 | No collisions, short codes, cheap writes | Needs a distributed counter, sequential codes are guessable |
Why Hashing Is Weaker Than It Looks
Seven base62 characters hold about 42 bits of entropy. By the birthday bound, collisions become likely once you store a few million URLs, far below your 182.5 billion. Every write then needs a read-before-write check or a conditional insert, plus a retry with a salt. It works, but it is extra latency and extra load on your busiest table.
The Counter With Range Allocation
The stronger answer is a counter that never collides. A single global counter is a bottleneck and a single point of failure, so allocate ranges instead:
- A coordination service (ZooKeeper, etcd, or a small database table with an atomic increment) hands each URL service instance a block, say 1 million IDs.
- The instance assigns IDs from its block in memory, with zero network calls per write.
- When the block runs out, it asks for the next one.
If an instance crashes, you lose the unused part of its block. That is fine, because the keyspace has trillions of codes to spare. A Snowflake-style ID (timestamp, machine ID, sequence) also avoids coordination, but a 64-bit ID encodes to about 11 base62 characters, so mention it as an alternative rather than the default.
Here is the encoding step:
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
def encode_base62(n: int) -> str:
if n == 0:
return ALPHABET[0]
chars = []
while n > 0:
n, rem = divmod(n, 62)
chars.append(ALPHABET[rem])
return "".join(reversed(chars))
def decode_base62(s: str) -> int:
n = 0
for ch in s:
n = n * 62 + ALPHABET.index(ch)
return n
Then handle the weakness. Sequential IDs let anyone walk through your links by incrementing codes. Fix it by passing the ID through a reversible permutation before encoding, such as multiplying by a large number coprime to 62^7 modulo 62^7, or a small Feistel network. Codes look random but stay unique. Raising this unprompted is a strong hire signal.
Know the design but freeze when the interviewer asks "what happens if the ID service goes down?" TechScreen runs invisibly during your Zoom, Google Meet, or Teams screen share and surfaces talking points for follow-ups like this. Start with 3 free tokens.
Caching and Read Scaling
With about 120,000 reads per second, the cache carries the system. Use a Redis or Memcached cluster with cache-aside: check the cache, read the database on a miss, then populate the cache with a TTL. LRU eviction suits this workload because link traffic is heavily skewed toward recent and viral links.
Cover three details to show depth:
- Hot keys. One viral link can overload a single cache shard. Add a small in-process cache on each redirect server, or replicate hot keys across shards.
- Negative caching. Bots hammer random codes. Cache "not found" results briefly so misses do not reach the database.
- CDN and multi-region. Serve redirects from edge locations or regional read replicas so users in other continents get low latency. The mapping is immutable once created, which makes caching it far and wide safe.
For a deeper treatment of eviction, consistent hashing, and replication, see our guide to designing a distributed cache.
Analytics Without Slowing Redirects
Analytics must never sit on the redirect's critical path. The redirect service writes a small event (code, timestamp, IP-derived region, user agent, referrer) to a queue like Kafka and returns immediately. Stream processors aggregate counts into an analytics store, typically a column-oriented database built for time-series queries.
If the queue is down, drop or buffer events locally rather than failing the redirect. Saying "redirect availability beats analytics completeness" shows you prioritize correctly.
Expiry and Custom Aliases
Expiry works in two layers. On read, check expires_at and return 410 if the link has expired. In the background, a cleanup job deletes expired rows, or you use the database's native TTL feature where one exists. Set cache TTLs no longer than the link's remaining life.
Custom aliases share the same table, so they can collide with generated codes. Two clean fixes:
- Require custom aliases to include a character the generator never produces, or a minimum length beyond 7.
- Insert with a conditional write ("only if code does not exist") and return 409 on conflict.
Also validate aliases against a blocklist of reserved paths such as api, admin, and login.
Minutes 42 to 45: Wrap-Up and Failure Modes
End with a 60-second summary and a list of what breaks first. Name the bottlenecks: the cache tier under a viral spike, the coordination service if ranges are too small, and database shards if partitioning is uneven. Then name what you would add with more time: malicious URL scanning on create, per-user quotas, and link-preview pages for safety.
If you hit a wall in the middle of this phase, use the recovery steps in what to do when you get stuck in an interview. Narrate your trade-offs as you go, so the interviewer can follow your reasoning.
Scorecard: What Earns Hire vs Strong Hire?
Interviewers grade the same handful of dimensions. This is our own synthesized rubric, not any company's official one, mapped to this specific question.
| Dimension | No hire | Hire | Strong hire |
|---|---|---|---|
| Requirements | Starts drawing immediately | Lists core functional and non-functional needs | Scopes out features explicitly and states the read-heavy priority |
| Estimates | None, or not used | Computes QPS and storage | Derives 7-character code length and cache size from the math |
| ID generation | "Use a hash" with no collision plan | Counter plus base62, or hash with collision handling | Range allocation, crash behavior, and anti-enumeration permutation |
| Data store | Names a database with no reason | Key-value store justified by access pattern | Discusses partition key, alias uniqueness, and separate analytics store |
| Read scaling | No cache | Cache-aside with LRU | Hot keys, negative caching, CDN or multi-region |
| Redirects | Unaware of status codes | Knows 301 vs 302 | Ties the choice to analytics, expiry, and takedowns |
| Ownership | Waits to be led | Responds well to prompts | Drives deep dives and names failure modes unprompted |
The pattern is consistent. Hire means correct. Strong hire means correct plus proactive about the parts that break at scale. The bar also rises with level, which is why the staff engineer interview guide expects you to discuss multi-region consistency and operational cost on a question this size.
Common Follow-Up Questions
Expect at least three of these after your main design. Prepare a two-sentence answer for each.
- "Should the same long URL always return the same short code?" Optional. You can add a secondary index on a hash of
long_urlto deduplicate, but it costs storage and an extra lookup. Many products give each user their own code for separate analytics. - "How do you stop people from shortening phishing links?" Scan URLs against threat lists on create, rate-limit by API key, and support fast takedowns. That last point is another reason to prefer 302s.
- "What if the coordination service fails?" Instances keep serving from their current block. Size blocks so a typical outage does not exhaust them, and run the coordinator as a replicated cluster.
- "How would you shard the data?" Hash the code to pick a shard, ideally with consistent hashing, so adding nodes moves only a fraction of keys.
- "How do you handle 10x traffic overnight?" Redirect servers scale horizontally, the cache absorbs reads, and the database mostly sees misses. The first thing to watch is hot keys.
How to Practice This Question
Run the full 45-minute script out loud at least twice with a timer, once alone and once with a peer who interrupts with follow-ups. Our guide on how to practice system design interviews covers structured mock formats. After the URL shortener feels automatic, move to questions that reuse the same building blocks with more pressure, such as designing a typeahead autocomplete system or the broader set in our backend engineer interview guide.
Practiced the script but worried about blanking live? TechScreen runs alongside your Zoom, Google Meet, or Teams session and gives you real-time support with estimates, trade-offs, and follow-up answers in your system design round. Try it with 3 free tokens.
Frequently Asked Questions
What is the best way to generate short URLs in a system design interview?
For most interviews, the strongest answer is a distributed counter that hands out ID ranges to each application server, then encodes each numeric ID in base62. It guarantees uniqueness without a collision check on every write, and a 7-character base62 code covers about 3.5 trillion IDs. Mention that sequential IDs are guessable and that you can scramble them with a reversible bijection if enumeration is a concern.
How many characters does a short URL need?
It depends on how many URLs you must store over the system's lifetime. Base62 gives 62 possible characters per position, so 6 characters cover about 56.8 billion codes and 7 characters cover about 3.52 trillion. At 100 million new URLs per day for five years you need roughly 182.5 billion codes, which means 7 characters is the right answer with plenty of headroom for growth.
Should a URL shortener use a 301 or 302 redirect?
A 301 is a permanent redirect, so browsers and intermediaries may cache it and later clicks may never reach your servers. That cuts load but breaks click analytics and makes expiry or edits unreliable. A 302 (or 307) is temporary, so every click hits your service and can be counted. In an interview, state the trade-off and pick based on whether analytics is a requirement.
Which database should a URL shortener use?
The core access pattern is a key-value lookup by short code with almost no joins, so a horizontally scalable key-value or wide-column store such as DynamoDB or Cassandra fits well. A sharded relational database like PostgreSQL also works and is easier to defend if you need strong uniqueness constraints for custom aliases. Interviewers care less about the brand and more about whether you justify the choice with the read-heavy, simple-lookup workload.
How long should I spend on a URL shortener system design interview?
A typical round is 45 to 60 minutes. A good 45-minute split is 5 minutes on requirements, 5 on estimates, 5 on the API, 5 on the data model, 10 on the high-level design, 12 on deep dives such as ID generation, caching, and analytics, and 3 on wrap-up. Leave room for the interviewer's follow-ups. Running long on requirements is the most common reason candidates never reach the deep dives that earn strong hire.
Is the URL shortener question too easy for senior engineers?
No. The base design is simple, which is exactly why interviewers use it to measure depth. Senior candidates are expected to go past the happy path into ID range allocation, hot-key caching, multi-region reads, abuse prevention, analytics pipelines, and failure modes. A senior who stops at a hash function and a single database will usually be scored at mid-level, even if everything they said was correct.
Ready to use AI assistance in your next interview?
TechScreen is the invisible AI assistant trusted by engineers interviewing at Google, Meta, Amazon, and hundreds of other companies. Start with 3 free tokens — no credit card required.
Ace your next interview →