Hirestack
Hirestack
Worked Example 15.6 · 6 of 15

Design a URL shortener (TinyURL / bit.ly)

📖 4 min read · 11 sections · May 2026
Asked at: Amazon Meta Google Atlassian

The setup

"Design a URL shortener. Given a long URL, return a short URL (e.g., bit.ly/abc123). Reverse: given a short URL, redirect to the long URL. 100 million URLs created per day, 10× reads vs writes. Short URLs should never collide. Service must be highly available."

This problem looks trivial but exposes core distributed-systems concepts: ID generation at scale, read-heavy caching, durability under load. The interview test is whether you can recognize the simplicity and design accordingly — not over-engineer it.

Clarifying questions

QuestionStory answer
Custom aliases (user picks the short code)?Allow, but skip auth — optional feature.
Analytics on clicks?Out of scope; just redirect.
Expiry on short URLs?Optional. Default: never expire.
HTTPS only?Yes; the redirect is HTTP 301 or 302.
What's "highly available" mean numerically?99.99% uptime — 1 hour downtime/year max.

Capacity estimation

Writes: 100M/day = ~1,200/sec avg; 5K/sec peak
Reads: 10× writes = ~12K/sec avg; 50K/sec peak
Storage: 100M × ~500 bytes/record = 50 GB/day, ~18 TB/year, ~50 TB over 3 years

Design Decision 1: shortcode generation

What length? With 62 alphabet (a-z, A-Z, 0-9):

At 100M URLs/day for 10 years: ~365 billion URLs total. 7 chars suffice; 8 chars gives huge headroom.

ApproachHowTradeoff
Random + collision checkGenerate random 7-char string; INSERT; on conflict, retrySimple; collision rate climbs as cardinality grows; eventually many retries; non-deterministic
Hash of URLSHA256(long_url), take first 7 chars (base62 encoded)Same URL → same shortcode (idempotent for repeat shortens); but doesn't allow different shortcodes for the same URL by different users; collisions inevitable
Sequential ID + Base62Distributed ID generator (Snowflake), encode to base62Guaranteed unique; no collision check needed; but reveals creation order; needs distributed ID infrastructure

Production answer: sequential ID with Base62 encoding. Why:

Design Decision 2: storage choice

OptionFit
Postgres (sharded by shortcode prefix)Workable. ~50TB across 16 shards. Operational overhead.
DynamoDBExcellent. KV access pattern; trivial to operate; auto-scales.
CassandraWorkable. KV is its strong suit. Operational complexity (3 nodes minimum, replication, repair).

For an interview, DynamoDB is the simplest answer for the read pattern. For self-managed, Cassandra. Both scale linearly.

Design Decision 3: read path caching

50K reads/sec at peak — significant for the DB. But the access pattern is heavily skewed: popular URLs (viral tweets, news links) get most reads.

TierCache sizeHit rate
In-process (Caffeine) per app serverTop 10K hot URLs per server~80%
Redis cluster (shared)Top 1M URLs~95% combined with L1
DB fallbackEverything~5% of total requests

With 95%+ L1+L2 hit rate, DB sees 50K × 0.05 = 2.5K rps. Trivial for DynamoDB at any tier.

The architecture

WRITE PATH:
Client ──POST /shorten {long_url}──► App tier ──► ID gen ──► DB INSERT
                                          │
                                          └──► Return short_url

READ PATH (the hot one):
Client ──GET /abc123──► App tier ──► L1 cache (in-process)
                            │              │
                            │              └─ miss → L2 cache (Redis)
                            │                            │
                            │                            └─ miss → DB lookup
                            │
                            └──► 301 redirect to long_url

Cross-examination round 1: same URL, different shortener

Interviewer: 1000 users shorten the same URL. Do they all get the same shortcode?
Two product decisions, both valid: (a) always create new shortcode → users can each track "their" shortened URL independently; (b) idempotent for repeated shorts → user identification matters. Pick (a): simpler, doesn't require a reverse lookup. If a feature later requires (b), maintain a separate "user-claimed" mapping. Most production short-link services go with (a).

Cross-examination round 2: malicious URLs

Interviewer: Someone shortens malicious URLs in bulk. How do you prevent abuse?
Multiple layers. (1) Rate limiting per IP/user at the API gateway. (2) URL scanning on creation: check against threat-intel feeds (Google Safe Browsing API, internal block lists). (3) Background reputation scoring: track click-through behavior; suspicious patterns → flag. (4) Suspended URLs: instead of redirecting to malicious site, show a warning interstitial. (5) Per-user creator reputation: new accounts have stricter rate limits; aged trusted accounts get more capacity.

Cross-examination round 3: high availability

Interviewer: 99.99% means <1h downtime/year. How do you achieve that?
Multi-region active-active. Each region has its own app servers, cache, and DB replica. URLs are partitioned: most URLs accessible from any region (via cross-region read replication). Geo-DNS routes users to nearest healthy region. Region failure: traffic shifts to another region. Cache misses temporarily increase but service continues. Write path: writes go to home region; replicated. RPO < 1 sec; RTO < 30 sec for region failure.

Takeaway

URL shorteners look trivial but expose real distributed-systems patterns: unique ID generation, cache-driven read scaling, and high availability under load. The non-obvious part is that this is fundamentally a read-heavy KV system, not a data-rich app. Treat it as such: optimize the read path with multi-tier caching; storage choice is the simpler problem.

The deeper lesson for staff-level thinking: recognize the simplicity. Don't over-engineer. A URL shortener doesn't need 5 microservices, a queue, an event bus, or eventual consistency tricks. It needs a fast KV store and aggressive caching. Add complexity only when justified by requirements. Many interview candidates fail this not by missing complexity, but by adding it where it doesn't belong.