What is a URL Shortener?

A URL Shortener is a system that converts a long URL into a much shorter, unique URL. When a user visits the short URL, the system redirects them to the original long URL.

 Original URL:https://tech.examadda.org/system-design-hld/hld-interview-url-shortener

Short URL:
https://exa.ly/aB3xP9

When someone opens https://exa.ly/aB3xP9, they are automatically redirected to the original URL.

Before jumping into the design, it's important to ask thoughtful questions to uncover hidden assumptions, remove ambiguities, and clearly define the scope of the system. This ensures both the candidate and the interviewer have a shared understanding of the problem before discussing the architecture.

Below is an example of how the conversation between the candidate and the interviewer might unfold.

Candidate: What is the expected scale of the system? Approximately how many new URLs will be shortened each day, and how many redirect requests should we expect?Interviewer: Let's assume 10 million new URLs are created per day, with a 100:1 read-to-write ratio.
Candidate: Which characters are allowed in the shortened URL?Interviewer: The short URL can contain uppercase letters (A–Z), lowercase letters (a–z), and digits (0–9).
Candidate: Should users be able to create custom aliases instead of auto-generated short URLs?Interviewer: Yes, but it's a nice-to-have feature. Focus on the core functionality first, and implement it if time permits.
Candidate: Should shortened URLs expire automatically after a certain period?Interviewer: Yes. Every short URL should have a default expiration time, but users should also have the option to specify a custom expiration date.
Candidate: Do we need to support analytics, such as tracking the number of clicks on each short URL?Interviewer: Yes, basic click count tracking is sufficient. Advanced analytics such as geographic location, device type, browser information, and referrer tracking are out of scope.

After gathering these requirements, we can summarize the system's functional and non-functional requirements before moving on to the design. This helps establish a clear scope and ensures the solution addresses the interviewer's expectations.

Functional Requirements

  1. Shorten URL
    • Given a long URL, generate a unique and compact short URL.
    • Each generated short URL must uniquely map to the original long URL.
  2. Redirect
    • When a user accesses a short URL, the system should quickly redirect them to the corresponding original URL.
  3. Custom Aliases
    • Allow users to specify a custom short code (e.g., example.com/my-link), provided it is available and meets validation rules.
  4. Link Expiration
    • Support automatic expiration of short URLs.
    • Every short URL should have a default expiration time, with the option for users to specify a custom expiration date or TTL (Time-to-Live).
  5. Basic Analytics
    • Track the total number of clicks for each short URL.
    • Advanced analytics (such as device type, browser, location, and referrer) are out of scope. 

Non-Functional Requirements

  1. High Availability
    • The system should remain highly available, with a target uptime of 99.99%, ensuring redirect requests continue to work even during failures.
  2. Low Latency
    • URL redirection should be extremely fast, with a target response time of less than 50 ms (p99).
  3. Uniqueness
    • Every short code must be globally unique.
    • Each short URL should map to exactly one long URL, with no collisions.
  4. Scalability
    • The system should scale horizontally to handle millions of URL creations and a much higher volume of redirect requests.
    • Assume a 100:1 read-to-write ratio, where redirect operations significantly outnumber URL creation requests.
  5. Durability
    • Once a short URL is created, its mapping must be reliably persisted and should not be lost, even in the event of server failures.
  6. Reliability
    • The system should consistently return the correct destination URL and gracefully handle failures without affecting user experience.
  7. Consistency
    • After a short URL is successfully created, it should be immediately available for redirection, ensuring users always receive the latest data.

2. Back-of-the-Envelope Estimation

Before designing the system, it's useful to estimate the expected traffic, storage, and capacity requirements. These calculations help us choose an appropriate architecture and identify potential bottlenecks.

Assumptions

  • New short URLs created: 10 million/day
  • Read-to-write ratio: 100:1
  • Redirect requests: 1 billion/day
  • Peak traffic factor: 3× average traffic
  • Data retention: 5 years

Traffic Estimation

Write Traffic (URL Shortening)

URLs created per day: 10,000,000

Average Write QPS

10,000,00024×60×60116 requests/second\frac{10,000,000}{24 \times 60 \times 60} \approx 116 \text{ requests/second}

Peak Write QPS (3×)

116×3350 requests/second116 \times 3 \approx 350 \text{ requests/second}

Estimated Capacity

  • Average Write QPS: ~116
  • Peak Write QPS: ~350

Read Traffic (URL Redirects)

Redirect requests per day: 1,000,000,000

Average Read QPS

1,000,000,00024×60×6011,574 requests/second\frac{1,000,000,000}{24 \times 60 \times 60} \approx 11,574 \text{ requests/second}

Peak Read QPS (3×)

11,574×335,000 requests/second11,574 \times 3 \approx 35,000 \text{ requests/second}

Estimated Capacity

  • Average Read QPS: ~11.6K
  • Peak Read QPS: ~35K

Observation: Redirect requests significantly outnumber URL creation requests, making this a read-heavy system. Therefore, optimizing read performance through caching and efficient database lookups is a key design consideration.

Storage Estimation

Assume each URL mapping stores the following information:

FieldApproximate Size
Short code7 bytes
Original URL (average)200 bytes
User ID (UUID)36 bytes
Expiration timestamp8 bytes
Creation timestamp8 bytes
Click count8 bytes
Additional metadata~33 bytes
Total per record~300 bytes

Storage Per Year

URLs created per year

10,000,000×365=3.65 billion URLs10,000,000 \times 365 = 3.65 \text{ billion URLs}

Storage required per year

3.65B×300 bytes1.1 TB3.65 \text{B} \times 300 \text{ bytes} \approx 1.1 \text{ TB}

Storage for 5 Years

1.1 TB/year×5=5.5 TB1.1 \text{ TB/year} \times 5 = 5.5 \text{ TB}

Estimated Storage

  • Per Year: ~1.1 TB
  • For 5 Years: ~5.5 TB

In practice, additional storage will be required for indexes, replication, backups, logs, and analytics data. Therefore, the actual storage requirement may be 2–3× higher than the raw data estimate.

Short Code Length Estimation

The system uses Base62 encoding, which includes:

  • 26 lowercase letters (a–z)
  • 26 uppercase letters (A–Z)
  • 10 digits (0–9)

Total characters:

26+26+10=6226 + 26 + 10 = 62

The total number of unique short codes is:

62N62^N

where N is the length of the short code.

LengthPossible Short Codes
6 characters62⁶ ≈ 56.8 billion
7 characters62⁷ ≈ 3.52 trillion

With approximately 3.65 billion new URLs generated per year, a 7-character Base62 code provides more than enough capacity for many decades of growth while keeping URLs short and user-friendly.


Key Takeaways

  • Average Write Traffic: ~116 QPS
  • Peak Write Traffic: ~350 QPS
  • Average Read Traffic: ~11.6K QPS
  • Peak Read Traffic: ~35K QPS
  • Storage Requirement: ~1.1 TB/year (~5.5 TB over 5 years)
  • Recommended Short Code Length: 7 Base62 characters, providing approximately 3.5 trillion unique combinations, which offers ample room for future growth.

3. Core APIs

The URL Shortener service exposes a small set of REST APIs that allow clients to create, access, and monitor shortened URLs. These APIs cover the core functionality required for a production-ready URL shortening service.

1. Create Short URL

Endpoint 

 POST /shorten

Creates a new short URL for a given long URL. Users can optionally specify a custom alias and an expiration date.

Sample Request

 {  "long_url": "https://www.example.com/articles/system-design/url-shortener",
"custom_alias": "url-design",
"expires_at": "2027-01-01T00:00:00Z",
"user_id": "550e8400-e29b-41d4-a716-446655440000"
}

Request Parameters

FieldRequiredDescription
long_urlYesThe original URL to be shortened.
custom_alias NoUser-defined short code (e.g., /my-link). Must be unique.
expires_atNoExpiration timestamp for the short URL. If omitted, the system uses the default expiration policy.
user_idNoIdentifies the user creating the short URL. Useful for ownership, analytics, and management features.

Sample Response

{
"short_url": "https://short.ly/url-design",
"short_code": "url-design",
"long_url": "https://www.example.com/articles/system-design/url-shortener",
"expires_at": "2027-01-01T00:00:00Z",
"created_at": "2026-07-09T10:30:15Z"
}

Possible Responses

Status CodeDescription
201 CreatedShort URL created successfully.
400 Bad RequestInvalid or malformed URL.
401 UnauthorizedUser authentication failed (if authentication is required).
409 ConflictThe specified custom alias already exists.
422 Unprocessable EntityThe request is valid, but business rules are violated (e.g., invalid expiration date).

2. Redirect to Original URL

Endpoint

GET /{short_code}

Looks up the given short code and redirects the client to the corresponding original URL.

Success Response

Default Redirect

302 Found
Location: https://www.example.com/articles/system-design/url-shortener

A 302 (Found) redirect is typically preferred because:

  • Every request reaches the URL Shortener service.
  • Click counts and analytics remain accurate.
  • Expired or disabled links can still be enforced.
  • The destination URL can be updated if necessary.

Optional Permanent Redirect

301 Moved Permanently
Location: https://www.example.com/articles/system-design/url-shortener

A 301 (Moved Permanently) redirect may be used for links that are guaranteed never to change.

Trade-off:

  • Faster for repeat visitors because browsers cache the destination.
  • However, cached redirects bypass the URL Shortener service, making click tracking and link updates impossible.

Behavior

ScenarioResponse
Short URL exists and is active302 Found (or 301, if configured)
Short URL has expired410 Gone
Short URL does not exist404 Not Found
Short URL is disabled403 Forbidden (optional)

3. Get URL Analytics

Endpoint

301 Moved Permanently
Location: https://www.example.com/articles/system-design/url-shortener

Returns basic analytics for a shortened URL.

Sample Response

{
"short_code": "url-design",
"short_url": "https://short.ly/url-design",
"long_url": "https://www.example.com/articles/system-design/url-shortener",
"total_clicks": 15482,
"created_at": "2026-07-09T10:30:15Z",
"expires_at": "2027-01-01T00:00:00Z",
"last_accessed_at": "2026-07-09T16:20:31Z"
}

Possible Responses

Status CodeDescription
200 OKAnalytics retrieved successfully.
401 UnauthorizedUser is not authorized to view analytics.
404 Not FoundShort URL does not exist.

API Summary

APIMethodPurpose
/shortenPOSTCreate a new short URL
/{short_code}GETRedirect to the original URL
/analytics/{short_code}GETRetrieve basic click analytics

These three APIs provide the core functionality of a URL Shortener. Additional APIs such as Update URL, Delete URL, List User URLs, and Bulk URL Creation can be added later as optional management features.

4. High-Level Design

At a high level, the URL Shortener system must support two primary operations:

  1. URL Shortening – Users submit a long URL and receive a unique shortened URL.
  2. URL Redirection – When a user accesses the shortened URL, the system redirects them to the original long URL.

Since the system experiences a 100:1 read-to-write ratio (approximately 1 billion redirect requests versus 10 million URL creation requests per day), the architecture should be optimized for read performance. To achieve this, we separate the write path (URL creation) from the read path (URL redirection), allowing each service to scale independently.

Note

Rather than presenting the complete architecture all at once, we'll build it incrementally by addressing one requirement at a time. This mirrors how you would explain the design during a Low-Level or High-Level Design interview.

4.1 Requirement 1: URL Shortening

When a user submits a long URL, the system should:

  1. Validate the request.
  2. Generate a unique short code (or validate a custom alias).
  3. Store the mapping between the short code and the original URL.
  4. Return the shortened URL to the client.

Components

The URL Shortener consists of the following core components.

1. Client

Clients are web browsers, mobile applications, or third-party services that interact with the URL Shortener.

They use:

  • POST /shorten → Create a short URL.
  • GET /{short_code} → Redirect to the original URL.

2. Load Balancer

The Load Balancer sits in front of all application servers and distributes incoming requests across multiple instances.

Its responsibilities include:

  • Distributing traffic evenly across servers.
  • Detecting unhealthy instances and rerouting traffic.
  • Enabling horizontal scalability.
  • SSL/TLS termination.
  • Basic request filtering such as rate limiting and IP filtering (optional).

Because both the write and read services are stateless, additional instances can be added behind the load balancer without affecting clients.

3. URL Generation Service (Write Service)

The URL Generation Service is responsible for all write operations.

Its responsibilities include:

  • Validating the incoming URL.
  • Checking whether the URL already exists (optional optimization).
  • Generating a unique short code.
  • Validating custom aliases.
  • Storing URL metadata such as:
    • Original URL
    • Short code
    • Expiration time
    • Creation timestamp
    • User ID
    • Click count (initialized to zero)

Since URL creation traffic is relatively low (~350 peak QPS), this service requires significantly fewer resources than the read path.

4. Redirection Service (Read Service)

The Redirection Service handles the majority of system traffic.

With approximately 35,000 peak redirect requests per second, it must be:

  • Stateless
  • Highly available
  • Horizontally scalable
  • Optimized for extremely low latency

For every incoming request, the service:

  1. Receives the short code.
  2. Looks up the corresponding long URL.
  3. Verifies that the URL exists.
  4. Checks whether the link has expired or been disabled.
  5. Updates the click count asynchronously (optional).
  6. Returns an HTTP redirect (302 by default, or 301 when appropriate).

To minimize database access, frequently requested mappings are cached in an in-memory cache such as Redis.

5. Database

The database stores the permanent mapping between short codes and long URLs.

Each record typically contains:

  • Short code
  • Original URL
  • User ID
  • Creation timestamp
  • Expiration timestamp
  • Click count
  • Status (active, expired, disabled)

The database should provide:

  • High durability
  • Fast primary-key lookups
  • High read throughput
  • Replication for high availability
  • Backup and disaster recovery

4.2 Requirement 2: URL Redirection

Once a short URL has been created, users should be able to access it and be seamlessly redirected to the original long URL.

Since URL redirection accounts for approximately 1 billion requests per day (around 35,000 peak QPS), the read path must be optimized for low latency, high throughput, and high availability.

To achieve this, we introduce two additional components.

Additional Components

1. Redirection Service (Read Service)

The Redirection Service is responsible for handling all URL lookup and redirect requests.

Because redirect requests vastly outnumber URL creation requests (100:1), this service is designed to be:

  • Stateless
  • Horizontally scalable
  • Highly available
  • Optimized for extremely low latency

For every incoming request, the service performs the following steps:

  1. Extracts the short code from the URL.
  2. Checks the cache for the corresponding long URL.
  3. If the mapping is not found in the cache, retrieves it from the database.
  4. Verifies that the link:
    • Exists
    • Has not expired
    • Has not been disabled or deleted
  5. Optionally updates the click count asynchronously.
  6. Returns an HTTP redirect:
    • 302 Found (default)
    • 301 Moved Permanently (optional, for immutable links)

Since the service is stateless, multiple instances can run behind a load balancer, allowing the system to scale horizontally as traffic grows.

2. Cache Layer (Redis)

Although the database stores the authoritative URL mappings, querying it for every redirect request would create unnecessary latency and significantly increase database load.

To improve performance, we introduce a distributed Redis cache.

The cache stores frequently accessed URL mappings in memory, enabling extremely fast lookups.

Benefits of using Redis include:

  • Sub-millisecond lookup latency
  • Reduced database load
  • Improved response times
  • Higher system throughput
  • Better scalability for hot (popular) URLs

For every redirect request:

  • Cache Hit: The long URL is retrieved directly from Redis, and the user is redirected immediately.
  • Cache Miss: The service queries the database, stores the result in Redis for future requests, and then redirects the user.

This Cache-Aside (Lazy Loading) strategy ensures that only frequently accessed URLs occupy cache memory while keeping the database as the source of truth.

5. Database Design

The database is one of the most critical components of a URL Shortener because it stores the mapping between short URLs and their corresponding long URLs. It must provide high availability, low-latency lookups, horizontal scalability, and durable storage.

5.1 Choosing the Database: SQL vs NoSQL

To select the appropriate database, let's evaluate the system requirements.

Requirements

Our database should be able to:

  • Store billions of URL mappings.
  • Handle simple key-value lookups efficiently.
  • Support a 100:1 read-to-write ratio.
  • Scale horizontally as traffic and data grow.
  • Provide high availability and fault tolerance.
  • Persist data reliably so URL mappings are never lost.
  • Avoid expensive joins and complex transactions.

SQL vs NoSQL Comparison

FeatureSQL DatabaseNoSQL Database
Data ModelRelationalKey-Value / Document / Wide-Column
Horizontal ScalingDifficultEasy
Read PerformanceGoodExcellent
Write PerformanceGoodExcellent
JoinsSupportedUsually not required
SchemaFixedFlexible
High AvailabilityModerateExcellent
Best FitComplex relationshipsSimple key-value lookups

Which Database Should We Choose?

For a URL Shortener, NoSQL is generally the better choice.

Reasons:

  • Every redirect is essentially a key-value lookup:
short_code  →  long_url
  • We don't perform joins between multiple tables.
  • The workload is overwhelmingly read-heavy.
  • We need to store billions of records.
  • Horizontal scaling is a primary requirement.
  • Popular NoSQL databases are optimized for low-latency reads and automatic partitioning.

Suitable choices include:

  • Amazon DynamoDB
  • Apache Cassandra
  • ScyllaDB
  • Redis (as a cache, not the primary database)

Interview Note

If the interviewer asks you to use a relational database, the system can also be implemented with PostgreSQL or MySQL by indexing the short_code column. However, for internet-scale URL shortening services, NoSQL databases are generally preferred because of their scalability and high throughput.

5.2 Database Schema

Although the URL Shortener has a relatively simple data model, the schema should support:

  • Fast URL lookups
  • User ownership
  • Link expiration
  • Basic analytics
  • Future extensibility

At a minimum, we need two collections (or tables).

1. URL Mappings

Stores the mapping between a short URL and its original destination.

FieldTypeDescription
short_codeString (Primary Key)Unique identifier for the short URL.
long_urlStringOriginal destination URL.
user_idStringID of the user who created the URL (optional for anonymous users).
created_atTimestampTimestamp when the URL was created.
expires_atTimestampExpiration timestamp (nullable if the link never expires).
click_countIntegerTotal number of redirects.
is_customBooleanIndicates whether the short code is user-defined.
statusEnumLink status (Active, Expired, Disabled).

Primary Key

short_code

Since nearly every redirect request performs:

GET /{short_code}

the short_code serves as the ideal partition (primary) key, enabling constant-time lookups.

Secondary Index

A secondary index on:

user_id

allows us to efficiently retrieve all URLs created by a specific user.

This is useful for features such as:

  • My URLs
  • Dashboard
  • Link management
  • Analytics

Example Record

FieldValue
short_codeAb3xYz7
long_urlhttps://example.com/articles/system-design/url-shortener
user_iduser_12345
created_at2026-07-09T10:30:15Z
expires_at2027-01-01T00:00:00Z
click_count15482
is_customfalse
statusACTIVE

2. Users (Optional)

If the system supports registered users, a separate Users table (or collection) can store account information.

FieldTypeDescription
user_idString (Primary Key)Unique user identifier.
emailStringUser's email address.
created_atTimestampAccount creation timestamp.
api_keyStringAPI key for programmatic access (optional).

This table enables features such as:

  • User authentication
  • Ownership verification
  • Personal dashboards
  • Link management
  • API usage
  • Rate limiting based on users

Data Access Patterns

The schema is optimized for the application's primary access patterns.

OperationQuery Pattern
Redirect URLshort_code → long_url
Create Short URLInsert new record
Check Custom AliasLookup by short_code
Get User's URLsQuery by user_id
Update Click CountIncrement click_count
Delete/Expire LinkUpdate status or expires_at

Why This Schema Works Well

  • Optimized for constant-time primary-key lookups, which represent the vast majority of requests.
  • Supports billions of URL mappings through horizontal partitioning.
  • Enables efficient retrieval of user-owned links using a secondary index.
  • Stores all metadata required for expiration, analytics, and future enhancements.
  • Simple, scalable, and well-suited for NoSQL databases such as DynamoDB and Cassandra, where each redirect is essentially a fast key-value lookup.

6. Design Deep Dive

Now that we've established the high-level architecture and database design, let's dive into the key design decisions that determine the system's scalability, performance, and reliability.

One of the most important challenges is generating unique short URLs.

6.1 Unique URL Generation

The URL generation strategy is the heart of a URL Shortener.

A good algorithm should satisfy the following requirements:

  • Unique – Every short code should uniquely identify a URL.
  • Compact – Short URLs should remain small and easy to share.
  • Fast – Code generation should introduce minimal latency.
  • Scalable – It should work efficiently across multiple servers.
  • Collision Resistant – Different URLs should rarely generate the same short code.
  • Distributed-Friendly – Multiple servers should generate codes without becoming a bottleneck.

There are several approaches for generating short URLs. Let's start with the simplest one.

Approach 1: Hashing + Encoding (Deterministic)

One of the simplest techniques is to hash the original URL and convert the hash into a compact, URL-friendly string.

Since the same input always produces the same output, this is a deterministic approach.

How It Works

Step 1: Canonicalize the URL

Before hashing, we first normalize the URL.

Without normalization, URLs that point to the same resource could generate different short codes.

For example:

https://Example.com
https://example.com/
https://example.com:443/

Although these URLs refer to the same website, they would produce different hashes if processed directly.

Typical canonicalization steps include:

  • Convert the domain name to lowercase.
  • Remove default ports (:80 for HTTP and :443 for HTTPS).
  • Normalize trailing slashes.
  • Remove unnecessary query parameters (if allowed by business requirements).
  • Normalize URL encoding.

After normalization:

https://example.com/

All equivalent URLs produce the same canonical form.

Step 2: Generate a Hash

Next, apply a cryptographic hash function to the canonicalized URL.

Common choices include:

  • MD5 (128-bit)
  • SHA-1 (160-bit)
  • SHA-256 (256-bit)

Example:

URL
https://example.com/courses/system-design

↓

MD5

06d68bdbae12cb1837095f97771be94c

The result is a fixed-length fingerprint of the original URL.

Step 3: Truncate the Hash

The complete hash is too large to use as a short URL.

Instead, we keep only the first few bytes.

Example:

MD5 Hash

06d68bdbae12cb1837095f97771be94c

↓

First 6 bytes (48 bits)

06d68bdbae12

Using 48 bits provides a good balance between compactness and collision probability.

Step 4: Encode Using Base62

The truncated hash is converted into a Base62 string.

Base62 uses the following characters:

0-9
A-Z
a-z

for a total of:

62 characters

Unlike Base64, Base62 does not contain characters such as:

+
/
=

which require escaping inside URLs.

This makes Base62 ideal for URL shortening.

Example Workflow

Suppose a user submits:

https://example.com/courses/system-design

Generate MD5

06d68bdbae12cb1837095f97771be94c

Keep First 6 Bytes

06d68bdbae12

Convert Hex to Decimal

06d68bdbae12
↓

7,518,539,197,970

Encode to Base62

28MoyG9a

Final short URL:

https://short.ly/28MoyG9a

Why 48 Bits?

A 48-bit number contains:

2^48
≈ 281 trillion

possible values.

After Base62 encoding, this becomes roughly an 8-character string.

Similarly,

LengthPossible Codes
6 characters56.8 billion
7 characters3.52 trillion
8 characters218 trillion

Even a 7- or 8-character Base62 code provides enough capacity for billions of URLs.

Advantages

Very simple to implement.

Completely deterministic.

The same URL always generates the same short code.

Multiple servers can generate short codes independently without coordination.

Duplicate URLs naturally produce the same short code.

Collision Problem

The major drawback is hash collisions.

Since we only use a portion of the hash, two different URLs can eventually generate the same short code.

Example:

https://abc.com/page1

↓

28MoyG9a
https://xyz.com/article

↓

28MoyG9a

Both URLs produce the same short code.

Although this is statistically rare, it becomes increasingly likely as the system stores billions of URLs (a consequence of the Birthday Paradox).

Collision Detection

When inserting a new mapping, the database should enforce uniqueness.

This can be achieved using:

  • A PRIMARY KEY (NoSQL)
  • A UNIQUE constraint (SQL)
  • A conditional write (e.g., DynamoDB's attribute_not_exists)

If the insertion succeeds, the short code is unique.

If it fails, a collision has occurred.

Collision Resolution Strategies

1. Rehash with a Salt 

Append a random salt or nonce before hashing.

hash = SHA-256(url + randomSalt)

If a collision still occurs, generate a new salt and try again until a unique short code is produced.

Pros

  • Produces completely different hash values.
  • Easy to implement.
  • Works well in distributed systems.

Cons

  • May require multiple attempts.
  • Requires checking the database for uniqueness.

2. Rehash with an Incrementing Counter

Instead of a random salt, append an incrementing counter.

hash(url + 1)

hash(url + 2)

hash(url + 3)

Continue until a unique short code is generated.

Pros

  • Deterministic.
  • Easy to reproduce.

Cons

  • May require multiple retries under heavy collisions.
  • Requires uniqueness checks.

Trade-offs

Although hashing is simple and deterministic, collision handling introduces additional complexity.

Whenever a collision occurs, the system must:

  1. Generate another candidate.
  2. Query the database (or perform a conditional insert).
  3. Retry until a unique short code is found.

As a result:

  • Additional database operations increase write latency.
  • The write path becomes more complex.
  • The solution is no longer fully stateless because uniqueness must be verified against the database. 

Approach 2: Global Counter (Non-Deterministic)

One of the simplest and most reliable approaches to generating short URLs is to use a globally increasing counter.

Instead of hashing the original URL, the system generates a unique numeric ID for every new URL and then converts that ID into a compact Base62 string.

Unlike the hashing approach, the generated short code does not depend on the input URL. Therefore, this is a non-deterministic approach.

How It Works

The system revolves around a Counter Service that generates a globally unique, monotonically increasing integer.

Every time a new URL is shortened, the service generates the next available ID, which is then encoded into a short, URL-friendly string.

Step 1: Generate a Unique ID

When the URL Generation Service receives a request, it asks the Counter Service for the next available ID.

The Counter Service can be implemented using technologies such as:

  • Redis
  • etcd
  • ZooKeeper

A common choice is Redis, which provides the atomic INCR command.

INCR global_counter

The INCR operation is atomic, meaning that even if thousands of servers request IDs simultaneously:

  • Every request receives a unique value.
  • No duplicate IDs are generated.
  • No additional locking logic is required in the application.

For example:

RequestGenerated ID
Request 11
Request 22
Request 33
Request 44

Because the counter only increments, uniqueness is guaranteed.

Step 2: Encode the ID

The numeric ID is then converted into a Base62 string.

Example:

Integer IDBase62 Code
1000g8
1,000,0004c9B
1,000,000,00015ftgG

Finally,

https://short.ly/15ftgG

is returned to the user.

Since Base62 is highly compact, even billions of URLs result in short codes that are only 6–7 characters long.

End-to-End Workflow

The complete sequence is as follows:

  1. Client submits a long URL.
  2. URL Generation Service requests the next ID from the Counter Service.
  3. Counter Service atomically increments the global counter.
  4. The unique ID is returned.
  5. The ID is Base62 encoded.
  6. The URL mapping is stored in the database.
  7. The short URL is returned to the client.
Client
   │
   │ POST /shorten
   ▼
URL Generation Service
   │
   ▼
Counter Service (Redis INCR)
   │
   ▼
Unique ID
   │
   ▼
Base62 Encoding
   │
   ▼
Database
   │
   ▼
Return Short URL

Advantages

Guaranteed Uniqueness

Since each ID is generated by an atomic counter, collisions are impossible.

No collision detection or retry logic is required.

Extremely Fast

Generating a short URL requires only:

  • One atomic counter operation
  • One Base62 encoding
  • One database insert

Redis can execute millions of INCR operations per second with sub-millisecond latency.

Simple Implementation

The algorithm is straightforward and easy to maintain.

There is no need for:

  • Hash functions
  • Collision handling
  • Retry loops
  • Salting

Compact Short Codes

The length of the short code grows logarithmically.

Even after generating billions of URLs, the code remains relatively short.

Limitations

 Centralized Bottleneck

Every URL creation request must contact the Counter Service.

As traffic grows, this service can become a throughput bottleneck.

Single Point of Failure (SPOF)

If the Counter Service becomes unavailable:

  • Existing short URLs continue to redirect normally.
  • However, new short URLs cannot be generated until the service is restored.

 Predictable IDs

Since IDs are sequential, users can easily guess nearby short URLs.

Example:

15ftgG
15ftgH
15ftgI

This may expose private or unlisted links through enumeration attacks.

Improving the Design

Production systems typically enhance this approach to improve scalability and security.

1. Sharded Counters

Instead of maintaining a single global counter, the system can use multiple independent counters.

For example:

  • 256 counters
  • 512 counters
  • 1024 counters

Each shard generates IDs independently.

A 64-bit ID can be divided into:

  • 10 bits → Shard ID (supports up to 1024 shards)
  • 54 bits → Local counter

The global ID is constructed as:

global_id = (shard_id << 54) | local_counter

Each shard can generate:

25418 quadrillion IDs2^{54} \approx 18 \text{ quadrillion IDs}

which is far beyond the requirements of most systems.

Benefits

  • Eliminates the single bottleneck.
  • Enables horizontal scaling.
  • Maintains global uniqueness.
  • Distributes write traffic across multiple nodes.

Trade-off

Managing shards introduces additional operational complexity, and each shard must be assigned a unique identifier to prevent ID overlap.

2. ID Obfuscation

Sequential IDs are easy to predict.

To make generated short codes appear random, the numeric ID can be transformed before Base62 encoding.

Common techniques include:

  • XOR masking
  • Feistel networks
  • Hashids
  • Format-preserving encryption (FPE)

For example:

Original ID

123456789

↓

Obfuscation

918273645

↓

Base62

K9xLm2P

The transformation is reversible, allowing the original ID to be recovered internally if necessary.

Benefits

  • Prevents users from guessing nearby URLs.
  • Makes short codes appear random.
  • Adds privacy without significantly affecting performance.

Trade-offs

AdvantagesDisadvantages
Guaranteed uniquenessCentralized counter can become a bottleneck
No collision detection requiredSingle point of failure unless replicated or sharded
Very fast generationSequential IDs are predictable
Simple implementationRequires additional mechanisms (sharding or obfuscation) for large-scale deployments

Approach 3: Distributed Unique ID Generator 

A Distributed Unique ID Generator (such as Twitter Snowflake) is the industry-standard approach for generating unique IDs in large-scale distributed systems.

Unlike the Global Counter approach, it does not rely on a centralized service. Instead, every application server can generate globally unique IDs independently.

This approach combines the advantages of high throughput, horizontal scalability, and low latency, making it ideal for internet-scale services.

How It Works

Each generated ID is a 64-bit integer composed of multiple fields.

┌─────────────────────┬────────────┬──────────────┐
│ 41-bit Timestamp    │ 10-bit     │ 12-bit       │
│                     │ Worker ID  │ Sequence No. │
└─────────────────────┴────────────┴──────────────┘

Together, these components guarantee that every generated ID is globally unique.

1. Timestamp (41 Bits)

The first 41 bits store the number of milliseconds elapsed since a custom epoch.

A custom epoch is simply a chosen start date, for example:

January 1, 2025

Using a custom epoch instead of the Unix epoch extends the usable lifetime of the ID space.

Since:

241 milliseconds69.7 years2^{41} \text{ milliseconds} \approx 69.7 \text{ years}

if the custom epoch starts in 2025, the system can continue generating unique IDs until approximately 2094.

The timestamp also provides another useful property:

  • IDs are generated in chronological order.
  • More recently created URLs generally have larger IDs.

2. Worker ID (10 Bits)

The next 10 bits identify the machine (or service instance) that generated the ID.

Since:

210=10242^{10} = 1024

the system can support up to 1,024 workers simultaneously.

Each worker generates IDs independently, eliminating the need for a centralized counter.

Worker ID Assignment

When a worker starts:

  1. It registers with a coordination service such as:
    • ZooKeeper
    • etcd
    • Kubernetes Lease API (modern alternative)
  2. The coordination service assigns a unique Worker ID.
  3. The worker retains that ID for its lifetime (or for a renewable lease period).

This guarantees that no two workers generate IDs using the same Worker ID simultaneously.

3. Sequence Number (12 Bits)

The final 12 bits store a local sequence number.

This counter tracks how many IDs the worker has generated during the current millisecond.

Since:

212=40962^{12} = 4096

each worker can generate up to:

  • 4,096 IDs per millisecond
  • ≈4.1 million IDs per second

without contacting any external service.

If more than 4,096 requests arrive within the same millisecond, the worker simply waits until the next millisecond before generating additional IDs.

Example ID Generation

Suppose:

ComponentValue
Timestamp1720501234567
Worker ID37
Sequence15

These values are packed together into a single 64-bit integer.

Example:

Timestamp
      │
      ▼
101001001...

Worker ID
      │
      ▼
100101

Sequence
      │
      ▼
0000001111

↓

64-bit ID

7653498234987234

The numeric ID is then encoded using Base62 to produce the final short code.

7653498234987234

↓

Base62

AbX92Lp

Final short URL:

https://short.ly/AbX92Lp

Advantages

Fully Distributed

Every worker generates IDs independently.

No centralized counter is required.

As traffic grows, new workers can simply be added.

 Highly Scalable

Adding additional workers increases write throughput almost linearly.

For example:

  • 100 workers
  • 500 workers
  • 1000 workers

can all generate IDs simultaneously.

High Availability

If one worker crashes:

  • Existing URLs continue to function.
  • Other workers continue generating IDs normally.

There is no single point of failure.

Extremely Low Latency

ID generation occurs entirely in memory.

No database calls.

No Redis calls.

No network round trips.

Typical generation time is measured in microseconds.

Time-Ordered IDs (K-Sortable)

Since timestamps occupy the most significant bits, IDs are roughly ordered by creation time.

This property is often called K-sortability.

Benefits include:

  • Efficient primary-key inserts.
  • Reduced index fragmentation.
  • Fast range queries.

Examples:

  • Get URLs created today.
  • Get URLs created last week.
  • Archive old URLs.

Limitations

Clock Synchronization

This approach assumes that system clocks move forward.

If a server's clock moves backward (due to an NTP correction or manual adjustment), duplicate timestamps could be generated.

Common mitigation strategies include:

  • Pausing ID generation until the clock catches up.
  • Using a monotonic clock where available.
  • Detecting clock rollback and failing fast.
  • Relying on cloud-managed time synchronization services.

Infrastructure Complexity

Workers require unique Worker IDs.

This typically involves a coordination mechanism such as:

  • ZooKeeper
  • etcd
  • Kubernetes Lease API

The system also benefits from reliable time synchronization using NTP or cloud time services.

Although this adds operational complexity, it enables excellent scalability and fault tolerance.

Comparison of URL Generation Strategies

StrategyAdvantagesDisadvantagesBest Use Case
Hashing + Base62Deterministic, stateless, duplicate URLs naturally produce the same short codeHash collisions require detection and retry logicSystems where URL deduplication is an important requirement
Global Counter + Base62Guaranteed uniqueness, simple implementation, fast ID generationCentralized bottleneck, single point of failure (unless replicated), predictable IDsSmall to medium-scale applications with moderate write traffic
Distributed ID Generator (Snowflake)Fully distributed, highly scalable, globally unique IDs, high throughput, time-ordered IDsMore complex infrastructure, requires worker coordination and clock synchronizationLarge-scale distributed systems requiring high availability and massive throughput

6.2 Fast URL Redirection

The Redirection Service is the most performance-critical component of a URL Shortener.

Every time a user clicks a short URL, the service must:

  • Resolve the short code to the original long URL.
  • Validate that the link exists and has not expired.
  • Redirect the user with minimal latency.

Since the system serves billions of redirect requests per day, even a few milliseconds of additional latency can significantly impact user experience and infrastructure costs.

Therefore, the entire read path must be optimized for speed, scalability, and high availability.


Choosing the Right HTTP Redirect Status Code

Before optimizing the read path, we must decide which HTTP redirect status code to use.

The choice between 301, 302, and 307 affects caching behavior, analytics, and system control.

301 Moved Permanently

A 301 (Moved Permanently) response tells browsers and intermediaries that the destination URL is permanent.

After the first request, browsers typically cache the redirect and send future requests directly to the destination URL without contacting the URL Shortener service.

Advantages

  • Extremely fast for repeat visitors.
  • Reduces traffic to the URL Shortener service.
  • Lowers infrastructure costs.

Disadvantages

  • Future requests bypass the service.
  • Click tracking becomes inaccurate because subsequent requests never reach the application.
  • The destination URL cannot be changed once browsers have cached the redirect.
  • Link expiration and disablement cannot be enforced for cached clients.

Best Use Cases

Use 301 only when the destination URL is guaranteed to remain unchanged.

Examples include:

  • Permanent documentation links
  • Static marketing pages
  • Canonical website redirects

302 Found / 307 Temporary Redirect

A 302 (Found) or 307 (Temporary Redirect) indicates that the destination may change in the future.

Therefore, the browser contacts the URL Shortener service every time the short URL is accessed.

Advantages

  • Accurate click counting and analytics.
  • Supports updating the destination URL.
  • Allows expired or disabled links to be enforced.
  • Provides full control over every redirect.

Disadvantages

  • Every request reaches the application.
  • Slightly higher latency compared to cached 301 redirects.
  • Higher infrastructure load.

302 vs. 307

Both are temporary redirects, but they differ in how HTTP methods are handled:

  • 302 Found: Browsers may change a POST request into a GET when following the redirect (historical behavior).
  • 307 Temporary Redirect: Preserves the original HTTP method and request body.

Since URL shorteners almost always redirect GET requests, 302 is the most common and practical choice.

Recommendation

For most URL shortening services:

  • Use 302 Found by default.
  • Reserve 301 Moved Permanently for links that are truly immutable.

A 302 redirect ensures that every click reaches the service, enabling:

  • Accurate analytics
  • Link expiration
  • URL updates
  • Access control
  • Abuse detection

Although it introduces a small amount of additional latency, it provides the flexibility required by modern URL shortening platforms.

Optimizing the Read Path

To achieve sub-50 ms latency at global scale, we design the read path as a multi-level cache hierarchy.

Each layer attempts to resolve the short URL before falling back to the next, slower layer.

This approach minimizes database access and delivers the fastest possible response.

Layer 1: Browser Cache

If a browser has previously received a 301 redirect, it may already know the destination URL.

In this case:

  • The request never reaches the URL Shortener service.
  • The browser redirects the user immediately.

This provides the lowest possible latency but comes at the cost of losing analytics and control.

For 302 redirects, browsers generally revalidate the redirect by contacting the service, allowing accurate tracking.

Layer 2: CDN / Edge Cache

For globally distributed users, a Content Delivery Network (CDN) is the next optimization layer.

Popular CDN providers include:

  • Cloudflare
  • Akamai
  • Fastly
  • Amazon CloudFront

The CDN maintains edge servers close to users around the world.

Cache Hit

If the redirect response is cached:

  • The CDN returns the redirect immediately.
  • Typical latency is 10–50 ms, depending on geographic proximity.

Cache Miss

If the redirect is not cached:

  • The request is forwarded to the origin (the URL Shortener service).

Using a CDN reduces latency for global users and significantly decreases traffic to origin servers.

Layer 3: Distributed In-Memory Cache (Redis)

Once the request reaches the application, the first lookup is performed in a distributed in-memory cache such as Redis.

The cache stores mappings in the form:

short_code → long_url

Cache Hit

If the mapping exists:

  • The long URL is returned immediately.
  • The redirect response is sent to the client.
  • The CDN may cache the response for future requests.

Typical lookup latency is sub-millisecond.

Cache Miss

If the mapping is not found:

  • The application queries the database.
  • The retrieved mapping is inserted into Redis using the Cache-Aside (Lazy Loading) pattern.
  • Future requests are served directly from Redis.

Redis dramatically reduces database load by keeping frequently accessed ("hot") URLs in memory.

Layer 4: Database (Source of Truth)

The database stores the authoritative mapping between short codes and long URLs.

Typical record:

short_code
long_url
expires_at
status
click_count

For each cache miss, the application:

  1. Retrieves the mapping using the short_code as the primary key.
  2. Verifies that the link:
    • Exists
    • Has not expired
    • Has not been disabled
  3. Stores the mapping in Redis.
  4. Returns the redirect response.

Because lookups are simple primary-key reads, NoSQL databases are an excellent fit.

Suitable options include:

  • Amazon DynamoDB
  • Apache Cassandra
  • ScyllaDB

These databases provide high throughput, horizontal scalability, and low-latency key-value lookups.

Once the mapping is retrieved from the database:

  1. The application stores it in Redis using the Cache-Aside (Lazy Loading) pattern, with an appropriate TTL (for example, 24 hours).
  2. Future requests for the same short code are served directly from Redis, avoiding unnecessary database lookups.
  3. The application returns an HTTP 302 Found redirect response to the client. If configured, the CDN may cache this redirect according to its cache policy, enabling subsequent requests to be served directly from the edge without contacting the origin server.

6.3 Supporting Custom Aliases

Custom aliases allow users to create human-readable short URLs instead of system-generated ones.

For example:

System-generated:
https://short.ly/Ab3xYz7

Custom alias:
https://short.ly/summer-sale

This feature is especially useful for:

  • Marketing campaigns
  • Social media sharing
  • Brand consistency
  • Easy-to-remember URLs

However, supporting custom aliases introduces additional challenges, including input validation, uniqueness, and concurrent requests (race conditions). These challenges must be handled carefully to maintain the correctness and reliability of the system.

1. API Design

The URL creation API should accept an optional custom_alias parameter along with the original long URL.

Sample Request

{
  "long_url": "https://example.com/products/summer-sale",
  "custom_alias": "summer-sale",
  "expires_at": "2027-01-01T00:00:00Z"
}

Behavior

  • If custom_alias is provided, the system attempts to reserve and use the specified alias.
  • If custom_alias is omitted, the service automatically generates a unique short code using the configured ID generation algorithm (e.g., Snowflake + Base62).

This keeps the API flexible while supporting both auto-generated and user-defined short URLs.

2. Rigorous Validation

Before creating a custom alias, the system should validate it against a set of predefined rules to prevent invalid input, routing conflicts, and malicious usage.

Performing these validations before interacting with the database reduces unnecessary database operations and improves overall system performance.

Character Set Validation

The alias should contain only URL-safe characters.

A commonly used regular expression is:

^[a-zA-Z0-9_-]+$

Allowed characters include:

  • Uppercase letters (A–Z)
  • Lowercase letters (a–z)
  • Digits (0–9)
  • Hyphen (-)
  • Underscore (_)

Examples:

ValidInvalid
summer-salesummer sale
offer_2026offer@2026
blackFridaysale!

Length Validation

To prevent abuse and maintain readability, enforce minimum and maximum length limits.

For example:

  • Minimum length: 3 characters
  • Maximum length: 50 characters

Examples:

AliasResult
go❌ Too short
summer-sale✅ Valid
this-is-a-very-long-custom-alias-that-exceeds-fifty-characters❌ Too long

Reserved Words Validation

The system should maintain a blocklist of reserved keywords that cannot be used as custom aliases.

These typically include:

  • Application routes (api, admin, login, logout)
  • Common pages (help, contact, about)
  • Static resources (robots.txt, favicon.ico)
  • Offensive or prohibited words (based on business requirements)

Blocking reserved words prevents users from hijacking critical application routes and ensures smooth request routing.

Validation Flow

Before accessing the database, the service performs the following validations:

  1. Validate the alias format using a regular expression.
  2. Verify that the alias length is within the allowed range.
  3. Check that the alias is not a reserved or prohibited word.
  4. If all validations pass, proceed to the uniqueness check.

By validating early, the system avoids unnecessary database operations and reduces backend load.

3. Ensuring Uniqueness and Handling Race Conditions

Ensuring that every custom alias is globally unique is the most critical part of supporting custom aliases.

The challenge becomes particularly important when multiple users attempt to reserve the same alias simultaneously.

The Race Condition Problem

Consider two users trying to create the custom alias:

https://short.ly/summer-sale

at exactly the same time.

Without Proper Synchronization

  1. User A checks whether summer-sale exists.
    • Result: Not found
  2. User B performs the same check before User A completes the insertion.
    • Result: Still not found
  3. User A inserts the record successfully.
  4. User B also attempts to insert the same alias.

If uniqueness is enforced only in application code, both requests may initially believe the alias is available, leading to duplicate records or inconsistent data.

This is a classic race condition, caused by separating the check and insert operations.


The Reliable Solution: Atomic Writes

Instead of implementing a separate "check, then insert" workflow, the application should rely on the database to enforce uniqueness using a single atomic write.

The custom alias is stored in the same short_code field used for auto-generated URLs, allowing both types of short URLs to share the same lookup mechanism during redirection.

short_code  →  long_url

The database guarantees that the insert succeeds only if the short_code does not already exist.

This approach eliminates race conditions because the existence check and insert occur as one indivisible operation.

Database-Specific Implementations

DynamoDB

Use a conditional write with PutItem.

Condition:
attribute_not_exists(short_code)

The insert succeeds only if the short_code is absent; otherwise, DynamoDB rejects the request.

Cassandra

Use a Lightweight Transaction (LWT).

INSERT ... IF NOT EXISTS

Cassandra ensures that only one request can successfully insert the alias.

Relational Databases (PostgreSQL / MySQL)

Define the short_code column as a PRIMARY KEY or add a UNIQUE constraint.

PRIMARY KEY (short_code)

or

UNIQUE (short_code)

If two requests attempt to insert the same alias simultaneously, the database accepts one request and rejects the other with a unique constraint violation.

6.4 Handling High Availability

High availability is a key requirement for a URL Shortener. Users expect short links to work 24×7, even during server failures, network outages, or regional disruptions.

Our target is 99.99% availability, which means the system should remain operational with minimal downtime while continuing to serve redirect requests reliably.

To achieve this, we employ several strategies.

1. Multi-Region Deployment

Deploy the application across multiple geographic regions to eliminate regional single points of failure and reduce user latency.

Each region contains:

  • Load Balancer
  • URL Generation Service
  • Redirection Service
  • Redis Cluster
  • Database (or database replica)

Traffic is routed using latency-based DNS (or geo-routing), ensuring that users are directed to the nearest healthy region.

Write Path

There are two common deployment models:

  • Single-Primary (Active-Passive): One primary region handles all write requests, while other regions serve read traffic and act as failover regions.
  • Multi-Primary (Active-Active): Multiple regions accept writes simultaneously (supported by databases such as DynamoDB Global Tables).

For interview simplicity, assume a Single-Primary architecture where the primary region processes all writes and replicates data to secondary regions.

Benefits

  • Reduced latency for global users.
  • Automatic failover during regional outages.
  • Improved fault tolerance and disaster recovery.

2. Database Replication

To prevent data loss and ensure continuous availability, the database should replicate data across multiple nodes and regions.

DynamoDB

Enable Global Tables.

Benefits include:

  • Automatic multi-region replication.
  • High availability across regions.
  • Managed failover by AWS.
  • Eventual consistency between regions, with updates typically propagating within seconds.

Cassandra

Configure a Replication Factor (RF) of 3, meaning each partition is stored on three different nodes.

For read operations, use:

LOCAL_QUORUM

This ensures:

  • High availability during node failures.
  • Strong consistency within a region.
  • Low-latency local reads.

3. Graceful Degradation

Even if part of the system becomes unavailable, the service should continue operating whenever possible instead of failing completely.

Database Failure

If the database is temporarily unreachable:

  • Continue serving redirect requests from Redis if the URL mapping is already cached.
  • Avoid interrupting the read path for popular URLs.

This allows most users to continue accessing existing short links despite a temporary database outage.

Write Requests

If the database is unavailable, new URL creation requests cannot be persisted safely.

Possible strategies include:

  • Queue write requests for asynchronous retry (if supported by business requirements), or
  • Return 503 Service Unavailable and ask clients to retry later.

For a URL Shortener, it is generally preferable to reject new URL creation rather than acknowledge a request before it has been durably stored.

Serving Cached Data

If a cached mapping exists in Redis, continue serving the redirect even if the database is unavailable.

If the cached entry has expired but is still available (stale cache), the system may temporarily serve the stale data (based on business requirements) instead of returning an error. This approach improves availability while accepting a small trade-off in freshness.

High Availability Summary

StrategyBenefit
Multi-Region DeploymentProtects against regional failures and reduces latency by routing users to the nearest healthy region.
Database ReplicationPrevents data loss and ensures data remains available even if individual nodes or regions fail.
Redis CacheAllows redirect requests to continue even during temporary database outages.
Graceful DegradationKeeps the system operational by serving cached data and handling failures without complete service disruption.
Stateless ServicesEnables horizontal scaling and rapid recovery by allowing failed instances to be replaced easily.

Key Takeaways

  • Deploy services across multiple regions to eliminate regional single points of failure.
  • Replicate data across nodes and regions using technologies such as DynamoDB Global Tables or Cassandra replication.
  • Use Redis to serve cached URL mappings during temporary database failures.
  • Degrade gracefully by serving cached (or stale) data when appropriate and retrying or rejecting writes safely.
  • Design all application services to be stateless, enabling automatic failover and horizontal scaling to achieve the target of 99.99% availability.

7. Follow-ups

With the core URL Shortener design complete, interviewers often ask about additional features that improve the system. Two of the most common follow-up questions are:

  • Supporting Link Expiration
  • Tracking Click Analytics

Let's begin with link expiration.

7.1 Supporting Link Expiration

Link expiration allows a short URL to become invalid after a specified time.

This feature is useful for:

  • Time-sensitive marketing campaigns
  • Event registrations
  • Password reset links
  • Invitation links
  • Temporary file sharing
  • Automatically cleaning up old data

Without an expiration mechanism, the database would continue growing indefinitely, increasing storage costs and eventually impacting system performance.

There are two common approaches to handling expired links:

  1. Active Deletion
  2. Passive Expiration (Lazy Expiration)

Approach 1: Active Deletion (Background Cleanup)

In this approach, a background worker periodically scans the database for expired links and removes them.

For example, a scheduled job may run every hour (or every day) to delete records whose expiration time has passed.

Example SQL

DELETE FROM url_mappings
WHERE expires_at < NOW();

The cleanup job can be scheduled using tools such as:

  • Cron
  • Celery Beat
  • Apache Airflow
  • Kubernetes CronJobs
  • AWS EventBridge Scheduler + Lambda

Advantages

Keeps the Database Clean

Expired links are removed regularly, preventing unnecessary storage growth.

Improves Query Performance

Removing stale records reduces the size of indexes and improves lookup efficiency over time.

Reduces Storage Costs

Only active URL mappings remain in the database, lowering long-term storage requirements.

Disadvantages

Expensive at Scale

Scanning billions of records on a fixed schedule can generate significant database load.

Although indexing expires_at helps, cleanup operations can still become costly in very large systems.

2. Passive Expiration (Real-Time Validation)

In the Passive Expiration approach, expired links are not immediately removed from the database. Instead, the application validates the link's expiration time whenever a redirect request is received.

This ensures that links become inaccessible exactly at their expiration time, without relying on a background cleanup job.

How It Works

When a user accesses a short URL:

  1. Retrieve the URL mapping from Redis or the database.
  2. Check whether the link has expired.
  3. If the current time is greater than expires_at, return 410 Gone.
  4. Otherwise, return an HTTP 302 Redirect to the original URL.

Example Logic

if url_mapping.expires_at and current_time > url_mapping.expires_at:
    return HTTP 410 Gone

return HTTP 302 Redirect(url_mapping.long_url)

Advantages

Real-Time Accuracy

The link becomes inaccessible immediately after its expiration time, regardless of when background cleanup occurs.


Low Overhead

Expiration validation is simply a timestamp comparison, adding negligible latency to each request.


Simple Implementation

No background scheduler or cleanup infrastructure is required to enforce expiration.


Disadvantages

Data Accumulation

Expired links remain in the database until they are explicitly deleted or archived, increasing storage usage over time.


Historical Data Management

Analytics queries must filter out expired links where appropriate, otherwise reports may include inactive URLs.


3. Recommended Approach: Hybrid Expiration Strategy

In production systems, the best solution is a hybrid approach that combines the strengths of both Active Deletion and Passive Expiration.


Step 1: Passive Expiration (Read Path)

Every redirect request performs a real-time expiration check.

This guarantees that users can never access an expired link, even if the record still exists in the database.

Benefits:

  • Immediate expiration enforcement.
  • Minimal latency.
  • No dependency on background jobs.

Step 2: Background Cleanup (Data Hygiene)

A low-frequency background job periodically deletes or archives links that expired long ago.

For example:

  • Daily
  • Weekly
  • Monthly

depending on business requirements.

This helps:

  • Reclaim storage space.
  • Keep indexes small.
  • Improve long-term database performance.
  • Reduce storage costs.

Why Use a Hybrid Approach?

Passive ExpirationActive Deletion
Immediate enforcementDatabase cleanup
No scheduling requiredFrees storage space
Accurate user behaviorImproves long-term performance

Together, they provide both correct behavior and efficient storage management.


4. Cache Consistency with Expiration

When introducing caching (Redis and CDN), it is essential to ensure that cached entries do not outlive the link itself.

Otherwise, users may continue receiving redirects even after the link has expired.


The Problem

Suppose a link expires in 5 minutes, but the Redis or CDN cache stores the redirect for 24 hours.

Link expires after:      5 minutes
Redis TTL:              24 hours
CDN TTL:                24 hours

After five minutes:

  • The database correctly marks the link as expired.
  • However, Redis or the CDN may continue serving the cached redirect.

As a result:

  • Users can still access an expired link.
  • Security policies are violated.
  • Analytics become inaccurate.

The Solution

Whenever the application caches a URL mapping, the cache TTL should never exceed the link's remaining lifetime.

The cache expiration should be computed as:

cache_ttl = min(link_remaining_lifetime, default_cache_ttl)

where:

  • link_remaining_lifetime = expires_at - current_time
  • default_cache_ttl = standard cache duration (e.g., 24 hours)

Example

Suppose:

  • Link expires in 10 minutes
  • Default Redis TTL = 24 hours

Then:

cache_ttl = min(10 minutes, 24 hours)
          = 10 minutes

Redis automatically evicts the entry when the link expires, ensuring it is never served after its expiration.


Benefits

  • Prevents expired links from being served by Redis or the CDN.
  • Maintains consistency between the cache and the database.
  • Preserves security guarantees.
  • Ensures users always observe correct expiration behavior.

7.2 Analytics: Click Count

A modern URL Shortener is not complete without analytics. The most fundamental metric is the click count—the number of times a shortened URL has been successfully accessed.

Click analytics provide valuable insights such as:

  • Total number of clicks on a short URL.
  • Performance of marketing campaigns.
  • Popular links and trending content.
  • Traffic patterns over time.
  • User behavior by country, device, browser, or referrer (advanced analytics).

However, recording clicks at scale presents challenges related to performance, consistency, and high write throughput.


What Is a Click?

A click is recorded when a user successfully accesses a short URL and the service returns a valid redirect to the destination URL.

Typically, a click is counted only when:

  • The short URL exists.
  • The link has not expired.
  • The redirect succeeds (HTTP 301 or 302).
  • The request is not identified as bot or crawler traffic (optional).
  • Duplicate requests from the same user within a short time window may be ignored (optional, depending on business requirements).

Requests resulting in 404 Not Found, 410 Gone, or other errors are generally not counted.


Click Logging Flow

When a user clicks a short URL, two operations occur:

  1. Redirect the user immediately (latency-sensitive).
  2. Record the click for analytics (write-intensive).

The redirect path must remain as fast as possible. Therefore, analytics should never block the redirect.

Instead, the two responsibilities are separated.

User
   │
   ▼
Redirect Service
   │
   ├── HTTP 302 Redirect ─────────────► User
   │
   └── Record Click (Async)

This decoupled architecture ensures users experience minimal latency while analytics are processed independently.


Design Approaches

Approach 1: Direct Counter Update (Simple)

The simplest implementation increments the click count immediately after a successful redirect.

Workflow

  1. User requests the short URL.
  2. Service redirects the user.
  3. Increment the click_count column in the database.

Example SQL:

UPDATE url_mappings
SET click_count = click_count + 1
WHERE short_code = 'AbX92Lp';

Advantages

  • Very simple to implement.
  • Click counts remain nearly real-time.
  • No additional infrastructure is required.

Disadvantages

  • Every redirect generates a database write.
  • High write amplification under heavy traffic.
  • Popular URLs may experience row-level contention.
  • Database becomes the bottleneck as traffic increases.

Best Use Case

Suitable for:

  • Small applications
  • Internal tools
  • Low-traffic systems

Approach 2: Buffered Counting (Recommended)

For production-scale systems, directly updating the database on every click is inefficient.

Instead, maintain click counters in Redis and periodically flush aggregated counts to the database.


Workflow

Step 1: Increment Redis Counter

Each successful redirect performs a fast atomic increment.

Redis

click:AbX92Lp = click:AbX92Lp + 1

Redis INCR is atomic and completes in sub-millisecond time.


Step 2: Background Aggregation

A scheduled worker periodically reads accumulated counters from Redis.

For each counter:

UPDATE url_mappings
SET click_count = click_count + :delta
WHERE short_code = :short_code;

After the update:

  • Reset or delete the Redis counter.
  • Continue accumulating new clicks.

Advantages

  • Dramatically reduces database writes.
  • Redis handles very high write throughput.
  • Eliminates row-level contention.
  • Easily scales to millions of clicks per second.

Disadvantages

  • Click counts displayed in the UI may lag behind real time by a few seconds or minutes, depending on the flush interval.
  • Requires a background worker for aggregation.

Best Use Case

Recommended for most production URL Shortener systems.


Approach 3: Event Streaming (Real-Time Analytics)

Large-scale platforms such as Bitly or Google Analytics typically use an event-driven architecture.

Instead of updating counters directly, every click generates an analytics event.


Workflow

User
   │
   ▼
Redirect Service
   │
   ▼
Kafka / Kinesis
   │
   ▼
Stream Processing
(Flink / Spark Streaming)
   │
   ▼
Analytics Database
(ClickHouse / Druid / Cassandra)
   │
   ▼
Analytics Dashboard

Components

  • Kafka / Amazon Kinesis
    • High-throughput event ingestion.
  • Apache Flink / Spark Streaming
    • Real-time aggregation and processing.
  • ClickHouse / Apache Druid / Cassandra
    • High-performance analytical storage.
  • Dashboard
    • Displays click counts, traffic trends, geographic distribution, device information, referrers, and other metrics.

Advantages

  • Near real-time analytics.
  • Supports complex queries such as:
    • Clicks by country.
    • Clicks by browser.
    • Clicks by device.
    • Hourly or daily trends.
    • Campaign performance.
  • Horizontally scalable to billions of events per day.
  • Raw click events remain available for future analysis.

Disadvantages

  • Significantly more infrastructure.
  • Higher operational complexity.
  • Increased deployment and maintenance costs.

Best Use Case

Ideal for:

  • Enterprise analytics platforms.
  • Large-scale URL shortening services.
  • Systems requiring detailed business intelligence.

Comparison of Approaches

ApproachAdvantagesDisadvantagesBest Use Case
Direct Counter UpdateSimple implementation, real-time countsDatabase write on every click, poor scalabilitySmall applications and low traffic
Buffered Counting (Redis)Fast, scalable, greatly reduces database writesSlight delay in displayed countsMost production URL Shorteners
Event StreamingReal-time analytics, flexible aggregations, horizontally scalableComplex infrastructure and higher operational costLarge-scale platforms requiring advanced analytics