System Design Arena
System Design: URL Shortener (like Bitly)
Walk through the URL shortener design the same way you would in an interview: clarify the ask, estimate scale, define APIs, and narrate the architecture before touching code.
Case Study
URL Shortener
Same conversation, diagrams, and wrap-up you expectβnow framed with clearer scaffolding and iconography.
Prompt
Design a URL shortener that marketing teams can rely on during large campaigns. Support vanity links, expiration, analytics, and absolute reliability for redirects.
Interview snapshot
- β’ Topic: URL Shortener
- β’ Expected depth: 45 - 60 minutes
- β’ Focus areas: APIs, scale estimation, resilient architecture
- β’ Wrap-up: risk, monitoring, disaster recovery
Key takeaways
- β’ Shorten long URLs with optional custom alias and TTL.
- β’ Availability target 99.99% (redirect is business critical).
- β’ POST /api/links { originalUrl, alias?, expiresAt? } - validates URL, rate limits per user, persists metadata, publishes analytics event.
ποΈ Interview mode
Practice this like a real interview
Don't read the answer first. Work the framework against the bare prompt, then compare against what the interviewer expected at each step.
Scorecard
What the interviewer is evaluating
Staff-level signal
Can the candidate identify that this is a read-dominated system (100:1) and let that single fact drive the cache, storage, and ID-generation decisions?
π§ Staff engineer judgment
A URL shortener at 100M URLs fits in one Postgres instance. Donβt reach for Cassandra until you can say which number forces you off a single primary.
Calibration
The common (bad) answer
Client β Load Balancer β API Server β Database
β Why this scores poorly: Technically correct and completely undifferentiated β it works for any CRUD app and shows zero system-design depth.
β What a strong answer adds
- State the read/write ratio first β redirects dwarf creations, so the cache is the real system.
- Explain short-code generation: counter + base62 vs random, and the collision story.
- Say where consistency matters (custom aliases) and where it does not (click counts).
- Give the redirect a latency budget and show how cache + CDN meet it.
- Only then talk about database scaling β and defend why a single primary might be enough.
Build it up
Step-by-Step Walkthrough
Most candidates jump straight to the final diagram. Interviewers want to watch it evolve β start naive, find the bottleneck out loud, fix it, repeat. Here is the same journey, stage by stage.
Start embarrassingly simple
One web server, one relational database, two endpoints. POST /links hashes the long URL into a short code and inserts a row; GET /{code} looks the row up and returns a 302 redirect.
This genuinely works β and saying so is a strength, not a weakness. It gives you a baseline to measure every later decision against.
?Say the numbers before scaling anything
100M new links a month is only ~40 writes/sec. A single Postgres instance yawns at that. The read path is the real story β a popular link can pull thousands of redirects per second on its own.
The first crack: how do you generate the short code?
Hashing the URL (MD5, take 7 chars) collides eventually, and two users shortening the same URL now share analytics. Auto-increment IDs leak your total link count and create a single point of coordination.
The interview-winning move: a dedicated ID service issuing unique 64-bit IDs (Snowflake-style), Base62-encoded into a 7-character code. No collisions by construction, no coordination on the hot path.
Reads outnumber writes ~100:1 β cache the redirect
Every redirect that touches the database pays a disk-adjacent latency price and burns connections. A Redis cache keyed code β long URL turns the overwhelming majority of redirects into a sub-millisecond memory lookup.
Links are immutable after creation, which makes this the friendliest caching problem imaginable: no invalidation story to defend beyond TTL + delete-on-expiry.
!The follow-up they will ask
What is your hit rate assumption? Link traffic is heavily skewed β a small fraction of links absorbs most clicks β so even a modest cache holds the hot set. Say βZipf-distributedβ and mean it.
Go global: push redirects to the edge
A user in Singapore should not cross an ocean for a 302. Terminate at edge POPs (CloudFront/Fastly), where the redirect can be answered from POP-local cache; origin only sees the misses.
This is also where rate limiting and abuse filtering live β malicious shorteners are a real operational problem, and interviewers reward you for raising it unprompted.
Analytics without slowing the redirect
Click counting must never sit on the redirect path. Fire an event into Kafka after the 302 is already on the wire; a consumer aggregates into an analytics store that owners query.
This buys you exactly the trade interviewers want named: the redirect stays fast, and click counts become eventually consistent β off by seconds, which nobody dashboarding clicks will ever notice.
?The closing move
End by re-stating the guarantees: sub-100ms redirects from POP cache, zero collision codes, analytics eventually consistent by design. A crisp summary of trade-offs is what separates a hire from a lean hire.
Blueprint
Architecture Diagram
The narrated components above, laid out as an actual flow β so you can see how a request moves through the system, not just read a list of pieces.
Read Path β GET /r/{hash}
Client
requests short URL
Edge POP
Cloudflare / Fastly
Redis
hash β URL
Aurora / Dynamo
cache-miss fallback
Cache hit returns a 302 in under 100ms without ever reaching the origin.
Write Path β POST /api/links
API Gateway
validate + rate limit
ID Generator
Snowflake β Base62
Metadata Store
persist link row
Kafka
publish click topic
Analytics Pipeline
Kafka
click events
Flink
stream aggregation
BigQuery
owner dashboards
Flow Diagram
How the API maps to this flow
- 1
POST /api/links { originalUrl, alias?, expiresAt? }β validates URL, rate limits per user, persists metadata, publishes analytics event. - 2
GET /r/{hash}β edge function that resolves hash, increments click counter async, returns 302. - 3
PATCH /api/links/{hash}β owner updates TTL, destination, status (active|paused). - 4
GET /api/links/{hash}/statsβ aggregates total clicks, referrers, geo (reads from analytics store).
?But why does this actually hold up at scale?
200K redirects/s -> 18B/day. With 95% cache hit, DB sees ~10K/s.
Video walkthrough
Beginner System Design Interview: Design Bitly
Hello Interview Β· ex-Meta staff engineer Β· 59 min
A full hour of the real thing β a staff engineer running the URL-shortener question end to end, including the follow-ups.
Work the prompt yourself first β guided practice grades your own answer step by step. Watching someone else design it feels like progress and teaches you far less.
Interview flow
Dialogue timeline
Interviewer
Give me the quick pitch for what this service must do.
Candidate
Users post a long URL and get back a short hash. Anyone visiting the hash must be redirected within 100 ms, and owners can manage TTLs plus analytics.
Interviewer
How do you size this?
Candidate
Assume 1M new links per day (~11 writes/s) but 200K redirects/s at peak events, so reads dwarf writes. Each metadata row ~500 bytes -> ~500 MB/day into storage.
Interviewer
Walk me through the critical APIs and data model.
Candidate
POST /api/links creates hashes, GET /r/{hash} handles redirects, PATCH toggles TTL/destination, and GET stats pulls analytics. Data model centers on a `links` table keyed by hash.
Interviewer
Scaling story?
Candidate
Edge cache first, then Redis (hash -> original URL), finally replicated SQL/NoSQL store. Hashes use Snowflake IDs encoded Base62 to avoid collisions. Analytics events stream via Kafka into a warehouse.
Scoping
Requirements & trade-offs
Functional Requirements
- βShorten long URLs with optional custom alias and TTL.
- βRedirect within 100 ms even during regional launches.
- βExpose owner controls: pause link, change destination, view click totals.
- βProvide abuse-report workflow for malicious links.
Non-Functional Requirements
- βAvailability target 99.99% (redirect is business critical).
- βHot links must survive regional outages via multi-region replicas.
- βAll writes idempotent; collisions prevented via Snowflake-style IDs.
- βDR: RPO < 5 min, RTO < 15 min using async replication.
Blueprint
Architecture modules
Module 1
API Endpoints
- β’POST /api/links { originalUrl, alias?, expiresAt? } - validates URL, rate limits per user, persists metadata, publishes analytics event.
- β’GET /r/{hash} - edge function that resolves hash, increments click counter async, returns 302.
- β’PATCH /api/links/{hash} - owner updates TTL, destination, status (active|paused).
- β’GET /api/links/{hash}/stats - aggregates total clicks, referrers, geo (reads from analytics store).
Module 2
Back-of-the-Envelope
- β’200K redirects/s -> 18B/day. With 95% cache hit, DB sees ~10K/s.
- β’Analytics log: 200K events/s * 150 bytes ~ 2.6 TB/day -> compress + tier to cold storage after 30 days.
- β’Redis cluster sized for 600K ops/s (3 replicas).
- β’Base62 7-char hash -> 3.5e12 combos, enough for decades.
Module 3
System Diagram Notes
- β’Edge POP (Cloudflare/Fastly) handles GET /r/{hash}; cache miss hits regional API tier.
- β’Write path: API GW -> app service -> ID generator -> metadata store (Aurora/Dynamo) -> publish click topic.
- β’Read path: Edge cache -> Redis -> DB fallback. Redirect worker refreshes caches on metadata change.
- β’Analytics: Kafka -> Flink -> BigQuery for dashboards.
Module 4
Design Playbook
- β’Clarify SLA, hash format, and allowed custom aliases.
- β’Explain collision avoidance + rate limiting to stop spam.
- β’Call out cache-invalidation strategy when destination changes.
- β’Discuss monitoring: synthetic redirects, latency heatmaps, analytics lag alerts.
- β’Conclude with compliance: GDPR deletion, audit logging, secure secret rotation.