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
- 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.
- Redirect
- When a user accesses a short URL, the system should quickly redirect them to the corresponding original URL.
- Custom Aliases
- Allow users to specify a custom short code (e.g.,
example.com/my-link), provided it is available and meets validation rules.
- Allow users to specify a custom short code (e.g.,
- 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).
- 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.
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
Peak Write QPS (3×)
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
Peak Read QPS (3×)
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:
| Field | Approximate Size |
|---|---|
| Short code | 7 bytes |
| Original URL (average) | 200 bytes |
| User ID (UUID) | 36 bytes |
| Expiration timestamp | 8 bytes |
| Creation timestamp | 8 bytes |
| Click count | 8 bytes |
| Additional metadata | ~33 bytes |
| Total per record | ~300 bytes |
Storage Per Year
URLs created per year
Storage required per year
Storage for 5 Years
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:
The total number of unique short codes is:
where N is the length of the short code.
| Length | Possible Short Codes |
|---|---|
| 6 characters | 62⁶ ≈ 56.8 billion |
| 7 characters | 62⁷ ≈ 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.
4. High-Level Design
At a high level, the URL Shortener system must support two primary operations:
- URL Shortening – Users submit a long URL and receive a unique shortened URL.
- 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:
- Validate the request.
- Generate a unique short code (or validate a custom alias).
- Store the mapping between the short code and the original URL.
- 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:
- Receives the short code.
- Looks up the corresponding long URL.
- Verifies that the URL exists.
- Checks whether the link has expired or been disabled.
- Updates the click count asynchronously (optional).
- 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:
- Extracts the short code from the URL.
- Checks the cache for the corresponding long URL.
- If the mapping is not found in the cache, retrieves it from the database.
- Verifies that the link:
- Exists
- Has not expired
- Has not been disabled or deleted
- Optionally updates the click count asynchronously.
- 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
| Feature | SQL Database | NoSQL Database |
|---|---|---|
| Data Model | Relational | Key-Value / Document / Wide-Column |
| Horizontal Scaling | Difficult | Easy |
| Read Performance | Good | Excellent |
| Write Performance | Good | Excellent |
| Joins | Supported | Usually not required |
| Schema | Fixed | Flexible |
| High Availability | Moderate | Excellent |
| Best Fit | Complex relationships | Simple 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_codecolumn. 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.
| Field | Type | Description |
|---|---|---|
short_code | String (Primary Key) | Unique identifier for the short URL. |
long_url | String | Original destination URL. |
user_id | String | ID of the user who created the URL (optional for anonymous users). |
created_at | Timestamp | Timestamp when the URL was created. |
expires_at | Timestamp | Expiration timestamp (nullable if the link never expires). |
click_count | Integer | Total number of redirects. |
is_custom | Boolean | Indicates whether the short code is user-defined. |
status | Enum | Link 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
| Field | Value |
|---|---|
| short_code | Ab3xYz7 |
| long_url | https://example.com/articles/system-design/url-shortener |
| user_id | user_12345 |
| created_at | 2026-07-09T10:30:15Z |
| expires_at | 2027-01-01T00:00:00Z |
| click_count | 15482 |
| is_custom | false |
| status | ACTIVE |
2. Users (Optional)
If the system supports registered users, a separate Users table (or collection) can store account information.
| Field | Type | Description |
|---|---|---|
user_id | String (Primary Key) | Unique user identifier. |
email | String | User's email address. |
created_at | Timestamp | Account creation timestamp. |
api_key | String | API 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.
| Operation | Query Pattern |
|---|---|
| Redirect URL | short_code → long_url |
| Create Short URL | Insert new record |
| Check Custom Alias | Lookup by short_code |
| Get User's URLs | Query by user_id |
| Update Click Count | Increment click_count |
| Delete/Expire Link | Update 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 (
:80for HTTP and:443for 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,
| Length | Possible Codes |
|---|---|
| 6 characters | 56.8 billion |
| 7 characters | 3.52 trillion |
| 8 characters | 218 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:
- Generate another candidate.
- Query the database (or perform a conditional insert).
- 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:
| Request | Generated ID |
|---|---|
| Request 1 | 1 |
| Request 2 | 2 |
| Request 3 | 3 |
| Request 4 | 4 |
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 ID | Base62 Code |
|---|---|
| 1000 | g8 |
| 1,000,000 | 4c9B |
| 1,000,000,000 | 15ftgG |
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:
- Client submits a long URL.
- URL Generation Service requests the next ID from the Counter Service.
- Counter Service atomically increments the global counter.
- The unique ID is returned.
- The ID is Base62 encoded.
- The URL mapping is stored in the database.
- 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:
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
| Advantages | Disadvantages |
|---|---|
| Guaranteed uniqueness | Centralized counter can become a bottleneck |
| No collision detection required | Single point of failure unless replicated or sharded |
| Very fast generation | Sequential IDs are predictable |
| Simple implementation | Requires 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:
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:
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:
- It registers with a coordination service such as:
- ZooKeeper
- etcd
- Kubernetes Lease API (modern alternative)
- The coordination service assigns a unique Worker ID.
- 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:
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:
| Component | Value |
|---|---|
| Timestamp | 1720501234567 |
| Worker ID | 37 |
| Sequence | 15 |
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
| Strategy | Advantages | Disadvantages | Best Use Case |
|---|---|---|---|
| Hashing + Base62 | Deterministic, stateless, duplicate URLs naturally produce the same short code | Hash collisions require detection and retry logic | Systems where URL deduplication is an important requirement |
| Global Counter + Base62 | Guaranteed uniqueness, simple implementation, fast ID generation | Centralized bottleneck, single point of failure (unless replicated), predictable IDs | Small to medium-scale applications with moderate write traffic |
| Distributed ID Generator (Snowflake) | Fully distributed, highly scalable, globally unique IDs, high throughput, time-ordered IDs | More complex infrastructure, requires worker coordination and clock synchronization | Large-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
POSTrequest into aGETwhen 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:
- Retrieves the mapping using the
short_codeas the primary key. - Verifies that the link:
- Exists
- Has not expired
- Has not been disabled
- Stores the mapping in Redis.
- 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:
- The application stores it in Redis using the Cache-Aside (Lazy Loading) pattern, with an appropriate TTL (for example, 24 hours).
- Future requests for the same short code are served directly from Redis, avoiding unnecessary database lookups.
- 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_aliasis provided, the system attempts to reserve and use the specified alias. - If
custom_aliasis 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:
| Valid | Invalid |
|---|---|
summer-sale | summer sale |
offer_2026 | offer@2026 |
blackFriday | sale! |
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:
| Alias | Result |
|---|---|
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:
- Validate the alias format using a regular expression.
- Verify that the alias length is within the allowed range.
- Check that the alias is not a reserved or prohibited word.
- 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
- User A checks whether
summer-saleexists.- Result: Not found
- User B performs the same check before User A completes the insertion.
- Result: Still not found
- User A inserts the record successfully.
- 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
| Strategy | Benefit |
|---|---|
| Multi-Region Deployment | Protects against regional failures and reduces latency by routing users to the nearest healthy region. |
| Database Replication | Prevents data loss and ensures data remains available even if individual nodes or regions fail. |
| Redis Cache | Allows redirect requests to continue even during temporary database outages. |
| Graceful Degradation | Keeps the system operational by serving cached data and handling failures without complete service disruption. |
| Stateless Services | Enables 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:
- Active Deletion
- 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:
- Retrieve the URL mapping from Redis or the database.
- Check whether the link has expired.
- If the current time is greater than
expires_at, return 410 Gone. - 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 Expiration | Active Deletion |
|---|---|
| Immediate enforcement | Database cleanup |
| No scheduling required | Frees storage space |
| Accurate user behavior | Improves 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:
- Redirect the user immediately (latency-sensitive).
- 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
- User requests the short URL.
- Service redirects the user.
- Increment the
click_countcolumn 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
| Approach | Advantages | Disadvantages | Best Use Case |
|---|---|---|---|
| Direct Counter Update | Simple implementation, real-time counts | Database write on every click, poor scalability | Small applications and low traffic |
| Buffered Counting (Redis) | Fast, scalable, greatly reduces database writes | Slight delay in displayed counts | Most production URL Shorteners |
| Event Streaming | Real-time analytics, flexible aggregations, horizontally scalable | Complex infrastructure and higher operational cost | Large-scale platforms requiring advanced analytics |