The URL Shortener (like bit.ly or TinyURL) is a classic LLD problem that tests your understanding of encoding schemes, collision handling, caching, and analytics. It's commonly asked at Google, Microsoft, Amazon, and startups. Here's a complete design walkthrough.
Core Requirements
- Shorten a long URL to a 6-8 character alias
- Redirect short URL to original URL
- Custom alias support
- URL expiration (TTL)
- Click analytics: total clicks, unique visitors, geographic data
- User accounts to manage URLs
Core Entities
- URL — original URL, short code, userId, createdAt, expiresAt
- ShortCodeGenerator — produces unique 6-char codes
- ClickEvent — timestamp, IP, userAgent, referrer
- Analytics — aggregated click stats per URL
- User — owns URLs, has quota limits
Short Code Generation
class Base62Generator implements CodeGenerationStrategy {
private chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
generate(url: string): string {
// MD5 hash → take first 8 chars → Base62 encode
const hash = md5(url).substring(0, 8);
return this.toBase62(parseInt(hash, 16));
}
private toBase62(num: number): string {
let result = "";
while (num > 0) {
result = this.chars[num % 62] + result;
num = Math.floor(num / 62);
}
return result.padStart(6, "0");
}
}Collision Handling
Two different URLs can hash to the same code. Always check and retry with a salt:
class URLShortenerService {
shorten(originalUrl: string, userId: string): string {
let code = this.generator.generate(originalUrl);
let attempt = 0;
while (this.urlRepo.existsByCode(code)) {
code = this.generator.generate(originalUrl + attempt++);
}
this.urlRepo.save({ code, originalUrl, userId });
return code;
}
}Analytics with Observer
class RedirectService {
redirect(shortCode: string, context: RequestContext): string {
const url = this.urlRepo.findByCode(shortCode);
if (!url || url.isExpired()) throw new NotFoundException();
// Fire analytics event asynchronously
this.eventBus.publish(new ClickEvent(shortCode, context));
return url.originalUrl;
}
}
class AnalyticsService implements EventHandler<ClickEvent> {
handle(event: ClickEvent) {
this.analyticsRepo.incrementClicks(event.shortCode);
this.analyticsRepo.recordVisitor(event.shortCode, event.ip);
}
}Common Questions
- "How do you handle expired URLs?" → Check expiry on redirect, return 410 Gone
- "How do you make redirects fast?" → Cache hot URLs in Redis with TTL
- "Same URL for same user?" → Hash (userId + originalUrl) to generate code
- "Custom aliases?" → Let user provide code, check availability first