Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›System Design›Design URL Shortener

Classic Design Problems

Design URL Shortener

A URL shortener is a service that maps a long URL to a unique, much shorter alias, facilitating easier sharing and tracking. It is a fundamental system design problem because it requires balancing high-throughput read operations with consistent, collision-free write performance. You should reach for this pattern whenever you need to implement alias-based redirection, such as in marketing trackers, microservices routing, or link management platforms.

Core Logic: Hashing and Mapping

At its most fundamental level, a URL shortener must perform a bijection between a long URL and a short code. We avoid using the raw long URL as the database key because long strings are inefficient for indexing and lookup operations. Instead, we generate a short, unique identifier, typically six to eight characters long, composed of base62 characters (A-Z, a-z, 0-9). The core reasoning is to reduce the storage footprint and optimize the lookup speed by keeping the indexed keys small. We use a hashing function to map the long URL, but because collisions can occur, we must implement a detection mechanism. If a collision is found, we append a salt or a sequence counter to the input URL and re-hash until a unique code is found, ensuring the mapping remains deterministic and reliable for future lookups in our persistent storage layer.

import hashlib, base64

def generate_short_code(long_url, salt=""):
    # Concatenate salt to handle potential hash collisions
    raw_hash = hashlib.md5((long_url + salt).encode()).digest()
    # Convert bytes to base64 and strip padding for a URL-friendly code
    encoded = base64.urlsafe_b64encode(raw_hash).decode()[:7]
    return encoded

# Example usage: converting a long URL to a short slug
print(generate_short_code("https://www.example.com/very/long/path"))

Database Strategy

A high-performance URL shortener relies heavily on read latency. Since the number of times a link is accessed typically far exceeds the number of times a new link is created, we prioritize read speed. A key-value store or a highly indexed relational database is ideal here. We store the mapping as a document containing the short code (our primary key), the long URL, the creation timestamp, and potentially metadata like user ID or expiration dates. Because we perform lookups by short code constantly, the database index must be highly optimized for this specific column. We avoid complex joins or secondary indexes to keep the storage engine lean. If the load is extreme, we horizontally partition our data based on the short code ranges to ensure that no single database node becomes a bottleneck for the system's traffic.

class URLStore:
    def __init__(self):
        # Simulation of an in-memory index for O(1) lookups
        self.db = {}

    def save_mapping(self, short_code, long_url):
        # Storing with a timestamp for potential cleanup/TTL jobs
        self.db[short_code] = {"url": long_url, "created": 1672531200}

    def get_url(self, short_code):
        return self.db.get(short_code, {}).get("url")

storage = URLStore()
storage.save_mapping("abc1234", "https://google.com")

Handling Scale with Counters

Relying purely on hashing can lead to collisions as the dataset grows, which requires constant database queries to check for existing codes. A more robust, collision-free approach is to use a distributed counter service or a global sequence generator. By assigning a unique, monotonically increasing integer to every new URL request, we can convert that integer to a base62 string. This removes the randomness of hashing and guarantees that every short code generated is unique without needing to check for collisions in the database. This approach requires a centralized, high-availability counter service (like an atomic incrementor) that acts as the source of truth for new assignments. While this adds a dependency on a sequence generator, the tradeoff is significant: zero collisions and high predictability in URL generation, which is far more scalable for systems expecting billions of links.

def encode_base62(num):
    # Map integer ID to base62 character set
    chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    res = []
    while num > 0:
        num, rem = divmod(num, 62)
        res.append(chars[rem])
    return "".join(reversed(res))

# Unique IDs prevent collisions entirely
print(encode_base62(1000000)) # Unique code for the 1-millionth link

Caching and Traffic Optimization

For a system that serves millions of users, querying the database for every single redirect would be prohibitively slow and expensive. We must implement a caching layer, such as an in-memory store like a distributed cache cluster, to keep the most frequently accessed URLs readily available. When a user requests a short URL, the application first checks the cache. If the key exists (a cache hit), we return the long URL immediately without hitting the primary database. If it is a cache miss, we fetch from the database, store the result in the cache, and then redirect the user. We should implement an eviction policy like Least Recently Used (LRU) to ensure that the cache capacity is always dedicated to the most active links. This dramatically reduces read latency and offloads the database, making the service responsive even during heavy traffic spikes.

cache = {}

def get_redirect_url(short_code, db):
    # Check cache first for rapid response
    if short_code in cache:
        return cache[short_code]
    # Fetch from database on miss
    long_url = db.get_url(short_code)
    if long_url:
        cache[short_code] = long_url # Populate cache
    return long_url

API Gateway and Redirection

The final stage of the user interaction involves the actual HTTP redirection. The server should return a 301 (Permanent Redirect) status code if the URL mapping is considered static, or a 302 (Found) status code if the intent is for temporary redirection or tracking analytics. The API gateway handles these requests by parsing the incoming path, stripping the short code, and issuing the redirect headers. We must ensure that our headers are lightweight and that the connection is terminated as close to the user as possible. By offloading the redirection logic to a global content delivery network (CDN) or an edge-based proxy, we can minimize the distance the request travels. This architectural decision ensures that even if our core database is undergoing maintenance, the high-traffic redirection paths remain available to users based on cached edge responses.

class Redirector:
    def handle_request(self, short_code):
        url = get_redirect_url(short_code, storage)
        if url:
            # 301 for permanent, 302 for analytics/temporary
            return f"HTTP/1.1 301 Moved Permanently\nLocation: {url}\n"
        return "HTTP/1.1 404 Not Found"

service = Redirector()
print(service.handle_request("abc1234"))

Key points

  • Base62 encoding is preferred over raw binary to keep the short URL character count minimized and readable.
  • Hash-based collision resolution is safer for distributed environments that avoid a single source of truth.
  • Distributed sequence generators eliminate the need for collision checks in the database layer entirely.
  • Caching is the most effective strategy for managing read-heavy traffic and reducing database throughput.
  • LRU eviction policies ensure the cache remains filled with the most popular, high-utility URL mappings.
  • HTTP 301 redirects are appropriate for permanent mappings, whereas 302 allows for better tracking of redirects.
  • Database partitioning by hash or ID range ensures that data storage scales horizontally as the total link count grows.
  • Edge-based redirection via CDNs can significantly reduce global latency by bringing logic closer to the end user.

Common mistakes

  • Mistake: Relying on a purely random string generation without a collision check. Why it's wrong: You will eventually hit collisions, leading to data overwrites or incorrect redirects. Fix: Use a hash function (like MD5 or SHA) and then truncate it, or use a base-62 encoding of an auto-incrementing database ID.
  • Mistake: Storing URL mappings in a traditional RDBMS without indexing the 'short_url' column. Why it's wrong: Lookups become O(N) linear scans as the table grows, causing extreme latency. Fix: Create a unique hash index on the 'short_url' column to ensure O(1) or O(log N) lookup times.
  • Mistake: Overlooking the need for caching layers. Why it's wrong: Direct database hits for every redirect will overwhelm the backend as traffic scales. Fix: Use a cache like Redis to store the most frequently accessed URL mappings, effectively reducing database read pressure.
  • Mistake: Failing to account for custom aliases or vanity URLs. Why it's wrong: Users often want to customize their links, which requires a system that supports both random generation and user-specified inputs. Fix: Design the schema to handle both auto-generated keys and user-defined keys, ensuring they don't collide.
  • Mistake: Neglecting the deletion or expiration policy for links. Why it's wrong: The database will grow indefinitely with orphaned or expired links, consuming expensive storage and degrading performance. Fix: Implement TTL (Time-To-Live) indices or a batch job to periodically clean up inactive or expired entries.

Interview questions

What are the primary functional requirements for designing a URL shortening service?

The primary functional requirements include URL redirection and URL shortening. First, when a user provides a long URL, the service should generate a shorter, unique alias for it. Second, when a user accesses the shortened URL, the system must redirect them to the original long URL. Additionally, we should provide an API for developers to manage these links and potentially allow users to define a custom alias if it is not already taken.

How would you estimate the traffic and storage capacity required for this system?

We should plan for high read-to-write ratios. Assuming 100 million new URLs per month and a 100:1 read-to-write ratio, we need to handle 10 billion redirections per month. In terms of storage, if each record is 500 bytes and we store links for five years, we need 500 million links multiplied by 500 bytes, which is roughly 250 gigabytes. This capacity is manageable on modern hardware, but we must use distributed storage for high availability.

Compare using a hashing algorithm versus a base-62 encoding approach to generate unique aliases.

Using a cryptographic hash function like MD5 or SHA-256 followed by base-62 encoding is one approach, but it suffers from potential collisions, requiring extra database checks to resolve them. Conversely, using a distributed ID generator or an auto-incrementing database sequence converted to base-62 is superior because it guarantees uniqueness by design. This avoids collision handling logic entirely, making the write path faster, more predictable, and easier to scale across multiple data centers.

How would you design the database schema for this service?

The schema should be simple to ensure high performance. We need a 'URL' table with an 'id' (primary key, auto-incrementing), a 'long_url' (varchar), and a 'created_at' (timestamp) column. For performance, we index the 'id' column. Because we read far more often than we write, a NoSQL store like Cassandra or DynamoDB is often preferred over relational databases to allow for easy horizontal scaling and high write throughput as we grow globally.

How would you implement caching to optimize the redirection process?

Caching is critical because popular URLs will be accessed millions of times. We should implement an LRU (Least Recently Used) cache, such as Redis or Memcached, sitting in front of the database. When a user requests a URL, the system first checks the cache. If the key exists, we return it immediately. If not, we query the database, update the cache, and then serve the result. This drastically reduces database read latency.

How would you handle the system's performance if the load becomes extremely high?

To handle massive load, I would implement a multi-layered strategy. First, load balancers distribute traffic across multiple application servers. Second, I would shard the database using the hash of the short URL to spread the load across different physical nodes. Third, I would use geographic replication to ensure that users access data from the closest data center, minimizing network latency and ensuring the system remains responsive under significant global pressure.

All System Design interview questions →

Check yourself

1. When designing a URL shortener that requires support for a massive number of links and high availability, which storage strategy is most scalable?

  • A.Use a single monolithic relational database with no horizontal sharding.
  • B.Use a NoSQL Key-Value store partitioned by the short URL key.
  • C.Store all mappings in a distributed file system to maximize disk space.
  • D.Rely entirely on in-memory storage for all historical link data.
Show answer

B. Use a NoSQL Key-Value store partitioned by the short URL key.
NoSQL Key-Value stores allow for horizontal partitioning and high-throughput read/writes, essential for global scale. Option 0 is a bottleneck; Option 2 introduces unnecessary latency; Option 3 is non-persistent and risks data loss on restart.

2. What is the primary benefit of using Base-62 encoding (0-9, a-z, A-Z) for generating short URLs?

  • A.It provides cryptographic security against link tampering.
  • B.It maximizes the number of possible unique URLs for a fixed length of characters.
  • C.It ensures that every URL generated is globally unique across all databases.
  • D.It compresses the actual content of the destination URL to save space.
Show answer

B. It maximizes the number of possible unique URLs for a fixed length of characters.
Base-62 increases the addressable space compared to decimal or hexadecimal, allowing for shorter URLs with more combinations. Option 0 is false as it's a representation, not encryption; Option 2 is handled by unique IDs; Option 3 is impossible without a compression algorithm.

3. If a system uses a counter-based approach to generate unique keys for URLs, what is a potential challenge in a distributed environment?

  • A.The counter might produce duplicate keys if multiple servers increment it simultaneously.
  • B.Base-62 encoding cannot process numerical values larger than 32-bit integers.
  • C.Counter-based approaches are too slow compared to UUID generation.
  • D.The system will fail to generate shorter URLs as time progresses.
Show answer

A. The counter might produce duplicate keys if multiple servers increment it simultaneously.
Distributed increments lead to race conditions; you need a centralized service or token ranges to prevent this. Option 1 is false (Base-62 is agnostic); Option 2 is false; Option 3 is false.

4. Why is it recommended to add a caching layer specifically between the application and the database for this system?

  • A.To perform real-time analysis of user demographics for every redirect.
  • B.To minimize the latency of the redirect process by serving popular links from RAM.
  • C.To encrypt URLs before they are written to the main database.
  • D.To act as a persistent backup for all shortened URL links.
Show answer

B. To minimize the latency of the redirect process by serving popular links from RAM.
Caching popular entries reduces latency and database read frequency, which is vital for high-traffic redirects. The other options are either not primary goals or are handled by other system components.

5. In a system design scenario, what is the trade-off of choosing a shorter 'short URL' length?

  • A.Shorter URLs provide better security against brute-force guessing.
  • B.Shorter URLs require more database storage space.
  • C.Shorter URLs reduce the total number of available unique combinations.
  • D.Shorter URLs make the system immune to network congestion.
Show answer

C. Shorter URLs reduce the total number of available unique combinations.
Shorter length reduces the keyspace, making collisions more frequent and limiting capacity. Longer keys offer a larger keyspace. Options 0, 1, and 3 are conceptually incorrect.

Take the full System Design quiz →

← PreviousDisaster Recovery and Backup StrategiesNext →Design Twitter / News Feed

System Design

31 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app