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.

Start Practice

The 5-Step System Design Framework

When entering any system design interview, follow these five steps to ensure a structured, collaborative, and successful discussion:

  1. Define the Requirements (Functional & Non-Functional)
  2. Identify Core Entities (High-level schema tables)
  3. Design the API (Endpoint routes, requests, and responses)
  4. High-Level Design (Drawing the core architecture)
  5. 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

http
POST /api/v1/urls
Content-Type: application/json

Request Body:

json
{
  "originalUrl": "https://example.com/blog/system-design-url-shortener",
  "customAlias": "url-shortener-guide",
  "expiresAt": "2026-12-31T23:59:59Z"
}

Response Body (201 Created):

json
{
  "shortUrl": "https://bit.ly/url-shortener-guide",
  "expiresAt": "2026-12-31T23:59:59Z"
}

2. Redirect a Short Code

http
GET /:shortCode

Response (302 Found):

  • Header Location: https://example.com/blog/system-design-url-shortener
  • Note: We use 302 Found (Temporary Redirect) instead of 301 Moved Permanently. A 302 ensures 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:

High-Level Design Architecture Diagram

  • 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_code column.
  • Caching (Redis/Memcached): Store the mapping of short_code -> original_url in 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!