01Should users be able to pick a custom alias, or is a system-generated short code enough?
Users can submit a long URL and get back a unique short link — system-generated base62 code (letters + digits) by default, custom alias as an optional extra.
Free full guide
Design a scalable URL shortener for 100M daily active users.
The interview rhythm stays compact, so the page can spend attention on the actual design decisions.
Do not only state requirements. Ask for them. Each card pairs the design constraint with a clarification question you can say out loud before drawing the architecture.
01Should users be able to pick a custom alias, or is a system-generated short code enough?
Users can submit a long URL and get back a unique short link — system-generated base62 code (letters + digits) by default, custom alias as an optional extra.
02Do links live forever, or do we need expiration and cleanup?
Users can set an optional expiration; an expired link stops redirecting and its code can be retired later.
03Is the redirect itself the core flow — does anything else have to happen on click?
Anyone who opens a short link is redirected to the original URL — by far the most frequent operation. Every click must also be recorded, and recording must never delay the redirect.
04Do marketing campaigns need bulk creation - thousands of links in one call?
Support batch creation: a campaign mints thousands of links in one request.
05Can a link be retargeted to a new destination after creation?
Links don't change once created. Retargeting to a new destination is an optional extra. After a retarget, visitors must land on the new destination, never the old one.
06Who owns a link - can creators list and deactivate their own?
Links belong to their creator: list, deactivate, and retire — a deactivated link must stop redirecting within seconds.
Out of scopeAnalytics dashboard — only async click-event emission stays in scope · User accounts, auth, and link-management UI · Spam and malicious-URL scanning
01What read/write ratio should I assume — is this heavily read-skewed?
Read-heavy: about 100M people follow a short link on a typical day, against about 1M new links created. That is at least 100:1 redirects to creates. Marketing-heavy deployments run closer to 1000:1, so it is worth asking which one this is.
02How fast must a redirect feel to the user?
Redirect p95 under 100 ms — the short-link hop must feel invisible to the person clicking.
03Must a brand-new link resolve instantly everywhere, or is a short delay fine?
Availability over consistency: redirects target 99.99%. A just-created link may take a few seconds to reach every cache and replica (eventual consistency accepted).
04How many total links should the code space and storage plan for?
Plan for ~1B links. A 7-char base62 code gives 62^7 ≈ 3.5 trillion possible codes, plenty of headroom. At ~1 KB per row, total storage stays near 1 TB, shardable by short code.
05Can two links ever share a code — and can someone guess private codes?
Short codes must be globally unique — no collisions, ever. For private links, codes must not be guessable: a predictable sequence would let a stranger enumerate private URLs one by one.
Real interviews probe far more than a tidy list. These are the scope questions that separate candidates who interrogate the problem from those who recite it.
Treat every estimate as a pressure that justifies a component: cache, queue, partition, replica, worker pool, or fallback path.
Redirect read QPS
100M daily users (the read-heavy sizing above) × ~1 redirect each per day = 100M redirects/day100,000,000 redirects ÷ 86,400 s in a day ≈ 1,160 QPS average · viral bursts ×5-10 ≈ 5-10K QPS peak
Cache + CDN must absorb the peak — the database never serves the redirect hot path.
Create write QPS
~1M new links per day1,000,000 ÷ 86,400 s ≈ 12 writes/s
Writes are trivial, so no write scaling is needed. The one write-side risk is fighting over code allocation, not the row inserts.
Total storage
1B links × ~1 KB per row (code, long URL, owner, timestamps, TTL)10⁹ × 1 KB ≈ 1 TB
Fits a sharded store comfortably; the short_code index is the real working set.
Code space
7-character base62 codes (62 characters per position)62⁷ ≈ 3.5 × 10¹² codes vs 10⁹ links needed → ~3,500× headroom
Codes never run out — the real risk is predictability (enumerable codes), not exhaustion.
Cache size
Assume ~20% of links serve ~80% of reads (Pareto)20% × 1 TB ≈ 200 GB
Feasible on a modest Redis cluster. The hottest viral links also sit on the CDN edge.
Decision example
The numbers point one way. Reads outnumber writes about 100 to 1, and peak at 5-10K redirects per second, each with under 100 ms to answer. So the redirect is the only path worth optimizing.
Answer redirects from a cache first — Redis, plus CDN for the hottest links. That way the database is never in the hot path. For creating links, a small key service hands each API server a batch of pre-made unique codes, so a create never fights over a shared counter. Clicks get counted by dropping an event onto a stream. The redirect never waits for analytics.
What I would NOT do: hash the long URL to make the code and retry whenever two URLs collide. Under load those retries pile up and creating a link becomes a lottery. Handing out pre-made unique codes makes collisions impossible instead of merely unlikely.
If accurate per-click analytics becomes a must-have, I would switch from 301 to 302 redirects. Then browsers stop caching the hop, so every click reaches my servers and gets counted. I accept the small extra latency.
One complete picture first, then each path as its own diagram — the write path and the read path carry different traffic and justify different components.
Complete picture
Start with the simple version: one picture, every component and relation. Dashed = asynchronous, off the hot path. A cache miss falls back to the database and refills the cache.
Path 1
KGS hands out pre-generated unique code ranges, so creates never collide and never block on a shared counter.
Path 2
Cache miss falls back to the database and refills the cache; click events go to the analytics stream off-path.
Before optimizing, make the contract inspectable: endpoints, entities, ownership, retries, and state.
POST/urls
req{ long_url, custom_alias?, expires_at? }
res200 { short_url, expires_at } · 409 alias taken · 400 invalid URL
Idempotent per (owner, long_url): resubmits return the existing short_url instead of minting a new code.
GET/{short_code}
res301/302 + Location: long_url · 404 unknown code · 410 expired
301 lets browsers cache the redirect (fewer origin hits, loses click analytics); 302 sends every click through your servers (keeps analytics, adds a hop). The choice hangs on the analytics requirement — say it out loud.
Core entities
ShortLinkshort_code (PK) · long_url · owner_id · created_at · expires_at · is_active
Shard by short_code so a redirect hits exactly one partition; an optional inverted index long_url → short_code enables create-time dedup.
Useruser_id (PK) · email · created_at
Pick one lane for the final third of the interview. Each lane gives you the topic, the interviewer question it should answer, and the failure mode to avoid.
How do you guarantee two links never get the same code — counter + base62, or hashing? Why that one?
Pre-mint the codes; don't hash. A separate service makes unique codes ahead of time and gives each API server its own block to hand out. Two servers can't pick the same code, and no one has to check the database first — even during a big campaign. Hashing only makes clashes rare, and every clash costs a retry when load is highest.
Hashing the URL and hoping collisions are rare, without explaining the uniqueness guarantee.
Walk one redirect end to end: where does the cache hit, what happens on a miss, and when does the database actually get touched?
Read the code from Redis first. A cache hit answers in a few milliseconds and never touches the database; a miss reads it once, then refills the cache. The hottest links also sit on the CDN edge. When a link is killed or retargeted, don't wait for the TTL — delete it from Redis and purge the CDN copy right away, so the change takes effect in seconds.
Reading the database on every redirect — the hot path must be cache-first.
Which redirect status do you return, and what does that choice do to browser caching and your click data?
Use 302 here, because we have to count every click. A 302 makes the browser ask the server each time, so every click is seen — the cost is one extra hop. A 301 lets the browser cache the redirect forever: faster, but repeat clicks go invisible. Since counting clicks is a requirement, 302 wins.
Picking one arbitrarily — this choice IS the analytics decision.
How do you record clicks for 100M DAU without adding latency, and what happens if the analytics pipeline falls behind?
The redirect drops a click event onto a durable stream like Kafka and returns immediately — the counting happens afterward, off to the side. If that pipeline falls behind, counts go stale for a while, but redirects never slow down. Stale numbers are an acceptable trade; slow redirects are not.
Writing analytics synchronously inside the redirect.
A single link suddenly takes 10% of all traffic — what breaks first, and how do cache and CDN absorb it?
A single viral link overloads the one cache shard that holds it, long before the database notices. Serve it from the CDN edge with a short TTL, so millions of hits are soaked up before they reach us. API servers can also keep it in local memory as a backup. However hard one link spikes, origin traffic stays flat.
Assuming traffic is evenly spread — hot keys are the real load.
Talk through URL Shortener out loud and get AI scoring on the explanation.