Design a URL shortener
Expected question
"Design a URL shortener (bit.ly-style). How do you generate short codes, redirect with low latency, and track analytics at scale?"
Variant forms
Same design, different framing:
- "Design a URL shortener for 100M new links/month and 10B redirects/month."
- "How do you generate short codes — hash, counter, or random — and avoid collisions?"
- "Design custom branded domains per enterprise customer."
- "Our short links were used for malware — architect abuse detection and takedown."
- "How do you expire links and support UTM analytics without slowing redirects?"
- "Design read-heavy redirect path with <10ms P99 globally."
- "Scale the analytics pipeline separately from the redirect hot path."
Where this actually gets asked
Canonical warm-up / screening system-design question (Bitly-style) across Google, Amazon, Meta, and startups. Often used before a harder follow-up in the same loop. Staff+ depth: ID generation, read-heavy caching, 301 vs 302, analytics, and abuse.
Executive summary
30-second thesis
I'd mint Base62 codes from allocated counter ranges, cache code→URL on the redirect hot path, default 302 + async click logs — and treat create-path abuse as a first-class concern.
2-minute answer
Clarify create QPS vs redirect QPS (often ~100:1 H), custom aliases, and analytics accuracy. Default: range-allocated IDs (or Snowflake → Base62), durable mapping, Redis/CDN in front of redirects. Prefer 302 when analytics matter or destinations can change; 301 only when immutable and you're okay with browser/CDN caching forever. Analytics off the hot path via queue. Custom aliases need a reservation transaction; reconcile if DB write wins and cache fill fails. Rate-limit creates; async malware scan with suspend. Viral codes: edge cache + shard awareness so one hot key doesn't melt a single DB partition.
Quantitative trade-offs
| Decision | Trade-off and reversal evidence | Evidence class |
|---|---|---|
| 302 vs 301 | 302 keeps counts/flexible targets; reverse to 301 when immutable and analytics are approximate OK. | H |
| Counter/Base62 vs hash-and-retry | Ranges avoid collision storms; reverse to hash only for tiny scale. | H |
| Cache-aside redirect vs always-hit DB | Cache for p99 <10–50ms (H); reverse only if consistency of destination changes forces shorter TTL + purge. | H |
What I'd ask them
- Can destinations change after create?
- Custom aliases and enterprise branded domains in v1?
- Redirect SLA and analytics lag tolerance?
Requirements
Functional
- Create a short code for a long URL; optional custom alias and TTL.
- Redirect short URL to long URL quickly.
- Basic click analytics (count, optional referrer/country aggregates).
Non-functional
- Extremely read-heavy (≈100:1 (H)); redirects P99 < 10–50ms with cache.
- Unique short codes; no collisions serving wrong destinations.
- High availability for redirects even if write path is degraded.
- Abuse: malware URLs, spam creation rate limits.
Core entities
- Link: short_code, long_url, owner_id, created_at, expires_at, status.
- Click event: short_code, ts, metadata (coarse).
- Alias reservation: custom code lock during create.
API / interface
POST /v1/links
Authorization: Bearer <token>
{ "url":"https://example.com/very/long", "custom_code?":"launch", "ttl_days?":90 }
→ 201 { "code":"aB3xY9", "short_url":"https://short.ly/aB3xY9" }
→ 409 alias_taken | 400 invalid_url
GET /{code}
→ 302 Location: <long_url> (or 301 if immutable / analytics-light)
→ 404 | 410 gone
GET /v1/links/{code}/stats
→ { "clicks":123456, "by_day":[...] }
Staff+ callout: say whether redirects are 301 (cacheable, weaker analytics) or 302 (accurate counts).
Data Flow
Create allocates code → persist mapping → cache. Redirect reads cache then DB; async click log.
Rendering architecture diagram…
High-level design
Rendering architecture diagram…
Deep dive 1: ID generation
Base62 encode a counter (Snowflake-style or pre-generated ranges per instance) beats hash-and-retry for uniqueness at scale. Custom aliases need a reservation transaction. See also 05.
Deep dive 2: cache and 301/302
Cache long URLs by code with TTL; negative-cache 404 briefly. 301 lets browsers/CDNs cache forever — cheap but bad for accurate analytics and destination changes. 302 + async analytics is the usual product choice for Bitly-like services.
Deep dive 3: abuse
Rate-limit creates per owner/IP; scan destinations against malware lists asynchronously (mark suspended). Cap custom alias namespace squatting.
Deep dive 4: viral hot links and create-path races
Shard by short_code; a viral link can melt one shard — edge/CDN cache + read replicas + redirect
rate limits. Custom aliases need transactional uniqueness; if DB write succeeds and cache fill
fails, a reconcile job must repair (avoid redirect 404). In 45 minutes, IDs + cache + 301/302 is
the core; analytics is async.
What's expected at each level
- Mid-level: hash URL, store map, redirect.
- Senior: Base62 IDs, cache, basic stats.
- Staff+: collision-free ID ranges, 301/302 trade-off, write/read HA split, abuse.
- Principal: global replication strategy and analytics cost at 100B redirects/day.
Follow-up questions to expect
- "How long should codes be?" — 62^7 is ~3.5T (H); pick length from years of creates × safety margin, not vibes.
- "Can destinations change?" — If yes, don't 301 (or purge CDN hard). Mutable targets want 302.