Design a URL Shortener
Welcome to the definitive guide on designing a URL Shortener (like Bitly or TinyURL). This is everyone's classic first system design question. In this guide, we will follow a structured, modern system design framework to scale our application to 100 million daily active users and 1 billion total shortened URLs.
Try This Problem Yourself
Practice with guided hints and real-time AI feedback.
The 5-Step System Design Framework
When entering any system design interview, follow these five steps to ensure a structured, collaborative, and successful discussion:
- Define the Requirements (Functional & Non-Functional)
- Identify Core Entities (High-level schema tables)
- Design the API (Endpoint routes, requests, and responses)
- High-Level Design (Drawing the core architecture)
- Deep Dives (Evolving the architecture to handle scale and optimizations)
Step 1: Requirements
First, clarify the requirements and scope of the system.
Functional Requirements
- Shorten URL: Take a long URL and generate a short code (e.g.,
bit.ly/Evan). - Redirection: Take a short code, look up the original URL, and redirect the user's browser.
- Custom Alias (Optional): Allow users to specify a custom short code if it isn't already taken.
- Expiration (Optional): Allow users to set an optional expiration date, after which the short URL is invalid.
Non-Functional Requirements
- Scale: Support 100 million daily active users (DAUs) and 1 billion URLs in total.
- Redirect Latency: Redirection must be sub-200 milliseconds (perceived as human real-time).
- Availability vs. Consistency: Prioritize High Availability over strong consistency (eventual consistency). Redirection must never go down.
- Uniqueness: Ensure every generated short code is unique to avoid collision and misdirected traffic.
Step 2: Core Entities
Keep the initial schema high-level, outlining only the persisted entities. We can combine them into a single table due to the simplicity of the relations.
1. URL Entity
id(Primary Key, integer or UUID)short_code(Unique Indexed String, the hash or custom alias)original_url(String, the destination link)created_at(Timestamp)expires_at(Nullable Timestamp)user_id(Foreign Key referencing the User)
2. User Entity
id(Primary Key)email(String)created_at(Timestamp)
Step 3: API Design
Define the RESTful contract between the client and your servers.
1. Create a Short URL
Request Body:
Response Body (201 Created):
2. Redirect a Short Code
Response (302 Found):
- Header
Location: https://example.com/blog/system-design-url-shortener - Note: We use
302 Found(Temporary Redirect) instead of301 Moved Permanently. A302ensures that the user's browser hits our servers on every single redirect, allowing us to collect accurate click analytics.
Step 4: High-Level Design
At a high level, the system architecture operates as a client-server-database relationship:

- Write Flow: The client posts a URL to the server. The server generates a unique short code, saves the record to the database, and returns the short link.
- Read Flow: The client hits
/shortCode. The server fetches the matching row from the database, checks if the expiration date is in the past, and redirects the user's browser.
Step 5: Deep Dives
To handle our target scale (100M DAUs) and latency requirements (under 200ms), we evolve our design.
1. Uniqueness & Code Generation (Base 62 Encoding)
To ensure system-generated codes are short (e.g., 6 characters), we use Base 62 Encoding (0-9, a-z, A-Z).
A 6-character code in Base 62 gives 62^6 (approx 56.8 billion) possible combinations—more than enough for our 1 billion URL limit.
To generate these codes:
- Option A: Hashing (MD5 or MurmurHash): Hash the original URL and slice the first 6 characters. If a collision occurs (which birthday paradox calculations indicate will happen about 88,000 times for 1B generations), check the database first. This requires an extra DB read before writing.
- Option B: Range-Based Counter (Recommended): Use a distributed counter. An coordinator cluster (like ZooKeeper) manages range blocks of size 1 million. Each application server fetches a block, increments a local counter for each new write, and encodes the counter value to Base 62. This guarantees 100% uniqueness without database roundtrips.
ZooKeeper Coordinator Optimization: By letting ZooKeeper assign range blocks of 1 million IDs, each application server only needs to request a new range once it exhausts its local block. This ensures zero cross-network synchronization overhead on the critical path of generating a short link.
2. Low Redirection Latency
Database lookups on disk are slow. We optimize redirects using:
- Database Index: Create a unique index (B-Tree index) on the
short_codecolumn. - Caching (Redis/Memcached): Store the mapping of
short_code -> original_urlin an in-memory cache. We use a Read-Through Cache with a Least Recently Used (LRU) eviction policy. Since only a small percentage of links are actively clicked, we cache only the "hot" URLs, keeping memory costs extremely low.
Read-Through Cache Optimization: Since URL redirection is extremely read-heavy (10:1 read-to-write ratio), caching the most active mappings in Redis guarantees sub-20ms lookup times for hot URLs, shielding our primary database from high load.
Now you're ready to ace your URL shortener system design interview! Happy coding!