How would you design a URL-shortening service?
A URL shortener is the "Hello World" of system design interviews. Simple on the surface. But once you start thinking about scale, collision handling, anal...
8 Mar 2024

A URL shortener is the "Hello World" of system design interviews. Simple on the surface. But once you start thinking about scale, collision handling, analytics, and expiration — it gets interesting fast.
I use this problem in workshops because it touches hashing, caching, database selection, and horizontal scaling in a compact design.
Business Requirements
- Short URL Generation — convert a long URL into a short, unique code. Support custom aliases ("mysite.co/launch-sale").
- Redirection — when someone visits the short URL, redirect them to the original URL. Fast. Sub-100ms.
- Analytics — track click counts, geographic distribution, referrer data, device types. This is where the real business value lives.
- Link Management — let users view, edit, and delete their links. Support expiration dates.
- High Availability — short URLs are shared in emails, social media, printed materials. If the service is down, every link is broken.
Technical Requirements
- Scalable Database — store billions of URL mappings. Handle high read throughput (reads dominate — every click is a read, but URL creation is infrequent).
- Efficient Shortening Algorithm — generate short, unique codes quickly. Handle collisions gracefully.
- Caching — most clicks go to a small set of popular URLs. Cache these for sub-millisecond redirects.
- Load Balancing — distribute traffic across multiple servers. No single point of failure.
- Rate Limiting — prevent abuse (spam, phishing links). Limit URL creation per user/IP.
Initial High-Level Design

This first design has problems:
- Single web server — single point of failure (SPOF).
- No horizontal scaling.
- Single database — won't handle 60TB of storage or 8,000 reads per second.
Improved Design

Add load balancers, multiple app servers, database sharding or replication, and a cache layer (Redis) in front of the database. Now you can scale each component independently.
The Shortening Algorithm: Base62 Encoding
The most common approach: generate a unique integer ID, then encode it in Base62.
Base62 uses [0-9a-zA-Z] — 62 characters. A 7-character code gives you 62^7 = 3.5 trillion possible URLs. That's plenty.
How it works:
- Generate a unique ID (auto-increment counter, or a distributed ID generator like Snowflake).
- Convert the ID to Base62.
- Use the result as the short code.
Example:
- Unique ID:
12345 - Base62 encoding:
"3D7" - Short URL:
https://sho.rt/3D7
For custom aliases, check uniqueness against the database. If the alias exists, reject the request.
The trade-off: auto-increment IDs are simple but predictable (users can guess other URLs by incrementing). Hash-based approaches (MD5/SHA256 truncated to 7 chars) are less predictable but require collision handling.
Database Selection
This is a read-heavy system. The read/write ratio is likely 100:1 or higher. Every click is a read. URL creation is comparatively rare.
Key-value stores (Redis, DynamoDB) are ideal for the core mapping: short_code → original_url. Fast lookups. Horizontal scaling. No complex queries needed.
Relational databases (PostgreSQL) work for user data, analytics, and link management where you need joins and transactions.
A common pattern: Redis as the primary lookup store (for speed), PostgreSQL as the source of truth (for durability and complex queries), with an async sync between them.
The Trade-Offs
Auto-increment IDs vs. hashes. Auto-increment is simple and collision-free but requires a centralized counter (bottleneck at scale) and is predictable. Hashes are distributed-friendly but need collision handling. UUIDs avoid collisions but produce longer codes. Choose based on your scale and security needs.
Caching vs. freshness. Cache short URL → long URL mappings aggressively. Most URLs never change after creation. But if you support URL editing or expiration, you need cache invalidation. For most shorteners, set a long TTL and invalidate on update.
Analytics: real-time vs. batch. Real-time click analytics add latency to the redirect path. Batch processing (log clicks, process later) keeps redirects fast. I'd log clicks to Kafka, process with a batch pipeline, and store aggregates in a time-series database.
Custom aliases: flexibility vs. abuse. Custom aliases are a great feature but open the door to squatting and abuse. Rate limit creation. Reserve common words. Implement a reporting mechanism.
The beauty of this problem is that it's simple enough to sketch in 10 minutes but deep enough to discuss for an hour. Every component — the ID generator, the cache layer, the analytics pipeline — has meaningful trade-offs worth exploring.