Design TinyURL — System Design Interview Guide
Design TinyURL is a system-design interview that asks you to build a URL shortener: users submit a long URL and get a 6-7 character short code; anyone visiting the short URL is redirected to the original. The hard part is generating unique short codes at write scale and serving redirects at read scale.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Reported in interviews at
- Amazon
- Microsoft
- Meta
- Twitter/X
Sourced from Glassdoor, Levels.fyi, and Blind interview reports.
Functional requirements
- Accept a long URL and return a short URL with a 6-7 character code
- Resolve a short URL via HTTP redirect to the original long URL
- Optionally allow custom-aliased short codes (e.g., /campaign-2024)
- Track click analytics (count, time, referrer, geo)
- Optionally support short-URL expiration
Non-functional requirements
- Read-heavy: ~100:1 ratio of redirects to creations
- Redirect latency: <50ms p99 from request to 301/302 response
- Availability: 99.99%; a short URL must redirect reliably for years
- Scale: ~100M new URLs/month, ~1B redirects/day, peak ~50K redirects/sec
Capacity estimation
Reasonable scale assumptions: 100M new short URLs/month = ~3.3M/day = ~40 writes/sec average, ~150 writes/sec peak. Redirects at 100:1 = 1B redirects/day = ~12K reads/sec average, ~50K peak. The system is firmly read-heavy.
Storage: each row is ~500 bytes (short code, long URL, owner, created_at, expires_at, click count). At 100M new rows/month over 10 years that's 12B rows = ~6 TB metadata (plus indexes ~2× that). Trivial compared to media-heavy products but still requires sharding for write QPS and index size.
Short-code length math: 7-character codes from a base-62 alphabet (a-z, A-Z, 0-9) yield 62^7 ≈ 3.5 trillion combinations. At 100M/month, the namespace exhausts in ~3000 years; 6 chars (62^6 ≈ 56 billion) exhausts in ~50 years — fine for v1, plan to extend to 7 before exhaustion.
Analytics writes: 1B clicks/day at peak ~50K/sec is a streaming-ingest problem, not a relational write — buffer in a streaming queue, aggregate offline.
High-level design
Two paths: write (create short URL) and read (resolve and redirect). Both go through an API gateway with rate limiting.
Write path: client posts a long URL to the create-shortlink service. The service generates a unique 7-character base-62 code, writes (short_code, long_url, owner, created_at) to a sharded relational store keyed on short_code, and returns the short URL to the caller. Code generation is the interesting choice — see deep dive below.
Read path: client (or HTTP user-agent) hits https://t.co/abc123. The redirect service looks up short_code → long_url. The lookup must be cached aggressively: most popular short URLs see Pareto traffic (top 0.1% of URLs serve 50% of clicks), so a small in-memory cache in front of the relational store absorbs 95%+ of reads. Cache miss falls through to the sharded store (sharded by short_code hash for even distribution). On a hit, return a 301 (permanent) or 302 (temporary, lets you change destinations) redirect to the long URL.
Analytics: redirects emit a click event (short_code, timestamp, referrer, ip, user_agent) to a streaming ingest queue. Aggregation workers compute per-URL click counts and per-day breakdowns into an analytics store. The hot redirect path never blocks on analytics writes — the queue absorbs spikes; if the queue is full, drop the analytics event (the redirect itself must always succeed).
Deep dive — the hard problem
The deep dive is code generation. Three viable strategies, each with tradeoffs.
Strategy A: random + collision check. Generate a random 7-char base-62 string; check the store; if collision, retry. Tradeoff: simple, but every create requires a read; at ~150 writes/sec peak this is fine, but collision rate grows as the namespace fills. At 1% of namespace consumed, collision probability is 1%; at 50%, it's 50% — collision retries dominate. Acceptable for v1 if you cap namespace utilization.
Strategy B: monotonic counter + base-62 encode. Maintain a global counter; on create, increment, encode the value in base-62. Tradeoff: zero collisions, fastest path, but the counter is a single point of contention. Solution: distribute counter ranges — each app server pre-allocates a block of 1000 IDs from a coordination service (a 'ticket server' pattern); local increments within the block need no coordination. Counter values are predictable, which can be a privacy/security concern for some use cases.
Strategy C: hash of long URL. Hash the long URL (SHA-1, truncate to 42 bits, base-62 encode). Tradeoff: deterministic — same long URL always gets the same short code (saves storage when many users shorten the same URL). But hash collisions become real if different long URLs map to the same short code; need a collision-resolution scheme (append a salt or rehash). Most production systems pick (B) for write scale and use (C) opportunistically for popular URLs.
Second deep dive: cache eviction. The cache is the most important component for redirect latency. Use LRU with a TTL, sized so the working set (top ~1M URLs) fits entirely in memory. Cache warming on deploy: prefetch the top N short codes from the analytics aggregation. For analytics-driven hot URLs, distribute cache load across read replicas to absorb traffic spikes (a viral tweet's short URL can spike to 100× normal).
Third tradeoff: 301 vs 302. 301 (permanent) lets browsers cache the redirect; subsequent clicks bypass your servers entirely — but if you ever need to change the destination, every browser holds the stale cache. 302 (temporary) means every click hits your servers (more load) but the destination remains under your control. Most production short-URL services pick 302 for control; mention you understand the tradeoff.
Common mistakes
- Choosing strategy A (random + check) at high write scale without bounding namespace utilization
- Using a global counter without distributing it — coordination service becomes a bottleneck at <10K writes/sec
- Forgetting the read cache — every redirect hitting the disk-backed store is unnecessary
- Designing analytics writes synchronously in the redirect path — kills redirect latency under spikes
- Returning 301 without acknowledging the cache-poisoning problem if destinations ever change
Likely follow-up questions
- How would you handle a single short URL going viral (1M clicks/minute)?
- What changes if you have to support custom domains (user.com/abc123)?
- How would you implement a 'preview' mode showing the destination before redirect?
- How would you handle short URLs that point to malware?
- How would you support short-URL expiration with millions of expirations per day?
Practice Design TinyURL live with an AI interviewer
Free, no sign-up required. Get real-time feedback on your design.
Practice these liveFrequently asked questions
- Is Design TinyURL too easy to be a real interview question?
- It's the most common 'warm-up' system design at FAANG, but it has real depth: the code-generation strategies trade off in non-obvious ways, and the read cache is load-bearing. Senior+ rounds extend it with analytics and custom domains.
- How long is the Design TinyURL interview?
- 45 minutes is standard. Some loops use it as a 30-minute first-round screen; senior rounds expand the analytics and personalization layers to fill 60.
- Do I need to mention specific hash functions?
- Naming SHA-1 or MD5 plus 'truncate and base-62 encode' is enough. The hash choice is not security-critical here (since this isn't crypto); just signal you know to truncate the output.
- What's the single most important property to optimize for in Design TinyURL?
- Read latency. The product's job is redirect-fast; a 200ms redirect kills the user experience. Cache aggressively, replicate reads, and accept eventual consistency on click analytics.