Tuesday, 25 August 2026

System Design - Design a URL Shortener like Bitly - Ramesh Style Explanation

Core Concepts:

1) short to long, long to shorting - asking encoding

2) http redirect concept

3) High Read Traffic - Horizontal Scaling

4) shortUrl unique - expect key sharding, means non-sql key

5) low latency - redis 



1. Design a URL Shortener like Bitly

Question: How would you design a scalable URL-shortening service?

Answer:

Requirements

  • Convert long URL → short URL.

  • Redirect short URL → original URL.

  • High read traffic, low latency.

  • Short links should be unique.

Architecture

Client
  ↓
Load Balancer
  ↓
API Servers
  ↓
Redis Cache
  ↓
Database

APIs

  • POST /shorten → returns short URL

  • GET /{shortCode} → HTTP redirect

Database

short_code | long_url | created_at | expiry

Short-code generation

  • Generate a unique numeric ID.

  • Encode it using Base62 (a-zA-Z0-9).

  • Example: 125789 → 8KxP

Scaling

  • Redis caches frequently accessed URLs.

  • Database is sharded using short_code.

  • Multiple stateless API servers behind load balancer.

  • Read replicas handle heavy read traffic.

Important interview point:
The system is read-heavy, so caching and horizontal scaling are critical.


2. Design a Distributed Rate Limiter

Question: How would you design a rate limiter for an API such as 100 requests/minute/user?

Answer:

Architecture

Client
  ↓
API Gateway
  ↓
Rate Limiter
  ↓
Application Services

Use Redis as the shared distributed state store.

For each user:

Key: rate_limit:user123
Value: request count
TTL: 60 seconds

When a request arrives:

  1. Redis INCR the counter.

  2. If first request, set TTL = 60 seconds.

  3. If count ≤ 100 → allow request.

  4. If count > 100 → return HTTP 429 Too Many Requests.

Why Redis?

  • Very fast.

  • Atomic INCR.

  • Shared state across multiple API servers.

  • TTL automatically resets the window.

Scaling

             ┌─ API Server 1 ─┐
Client → LB ─┼─ API Server 2 ─┼→ Redis Cluster
             └─ API Server 3 ─┘

For more sophisticated traffic control, use Token Bucket or Sliding Window algorithms instead of a simple fixed window.


-----------------------

Sure. For a URL Shortener, the most important part to understand is how the short URL gets converted back to the long URL, and where caching fits.

1. Basic flow

Suppose the user has:

Long URL:
https://www.amazon.in/products/mobile/samsung-galaxy-s24-ultra?id=12345

We generate:

Short URL:
https://bit.ly/aB72x

When someone clicks https://bit.ly/aB72x:

Browser
   ↓
GET /aB72x
   ↓
Load Balancer
   ↓
URL Service
   ↓
Redis Cache
   ↓
Database (if cache miss)
   ↓
Long URL
   ↓
HTTP 301/302 Redirect
   ↓
Amazon Long URL

2. Where is the long URL stored?

Database:

short_codelong_url
aB72xhttps://amazon.in/products/mobile/...

The short code is the key.

Conceptually:

Map<String, String>

"aB72x" → "https://amazon.in/products/mobile/..."

But in a distributed system, we don't keep this only in application memory. We use a persistent database.


3. Why Redis cache?

Imagine aB72x is clicked 1 million times.

Without cache:

1 million requests
        ↓
1 million DB queries

That's expensive and creates database pressure.

With Redis:

1 million requests
        ↓
      Redis
        ↓
   Long URL

Only the first request may need the database.

Request 1
   ↓
Redis MISS
   ↓
Database
   ↓
Long URL
   ↓
Store in Redis

Then:

Request 2
Request 3
Request 4
...
   ↓
Redis HIT
   ↓
Long URL

4. What exactly is cached?

Redis could contain:

KEY                 VALUE
-----------------------------------------------
url:aB72x           https://amazon.in/products/...
url:X7pK2           https://flipkart.com/product/...

So:

GET url:aB72x

returns:

https://amazon.in/products/mobile/...

5. How does redirect happen?

The URL service gets the long URL from Redis:

aB72x
  ↓
Redis
  ↓
https://amazon.in/products/mobile/...

Then the server responds:

HTTP/1.1 302 Found
Location: https://amazon.in/products/mobile/...

The browser receives the Location header and makes another request to the long URL.

So the URL-shortener server doesn't actually fetch Amazon's page.

It simply says:

"Go to this URL."


6. 301 vs 302

This is an important interview question.

301 — Permanent Redirect

Short URL → Long URL

Means the redirect is permanent. Browsers/proxies may cache the redirect.

302 — Temporary Redirect

Means the redirect may change later.

For a URL shortener, 302 is often safer if you want the destination to remain changeable.


7. Cache miss vs cache hit

This is the key interview explanation:

                 Request /aB72x
                       ↓
                    Redis
                   /     \
               HIT         MISS
                ↓            ↓
            Long URL       DB
                             ↓
                         Long URL
                             ↓
                       Store in Redis
                             ↓
                         Redirect

Interview answer in one sentence

Redis caches the mapping between short code and long URL, so frequently accessed short URLs can be resolved without hitting the database, reducing latency and database load; once resolved, the service returns a 301/302 response with the long URL in the Location header, and the browser performs the redirect.

-------------------

TinyURL System Design — Database & Scaling Class Notes

1. Database is a critical HLD decision

After defining the APIs and major components, the next important question is:

How will I store the data, and how will the database scale?

There are two major choices:

  • SQL

  • NoSQL

General rule of thumb

RequirementPreferred DB
Strong consistency + ACID transactionsSQL
Very high scalability + availabilityNoSQL
Complex joins/relationshipsSQL
Simple key-based lookupsNoSQL
Eventual consistency is acceptableNoSQL

For TinyURL, NoSQL is a good choice because the primary operation is a simple lookup:

short_url → long_url

We don't need complex joins or transactions.


2. Start with API → Query → Schema

A very important system-design technique:

API
 ↓
Queries
 ↓
Schema
 ↓
Database

Don't start by saying:

"I'll use MongoDB."

Instead ask:

What queries does my application need to perform?

Then design the schema around those queries.


3. TinyURL APIs

Create short URL

POST /shorten

Input:

longUrl = https://example.com/very/long/url/...

Output:

shortUrl = https://tinyurl.com/aB72x

This requires an INSERT.

long URL → short URL

Redirect API

GET /aB72x

The system needs to find:

aB72x → https://example.com/very/long/url/...




This is a READ operation.

Therefore the most important lookup key is:

short_url

4. TinyURL Schema

A simple NoSQL document could look like:

{
  "short_url": "aB72x",
  "long_url": "https://example.com/very/long/url",
  "created_at": "2026-08-26T10:00:00",
  "expiry_at": "2036-08-26T10:00:00"
}

Think of it as:

short_url  →  long_url

with additional metadata.


5. Why short_url should be a key

Our most common query is:

GET /aB72x

So internally:

Find document WHERE short_url = "aB72x"

Therefore:

short_url = Primary Key

This gives efficient lookup.

Instead of scanning millions of records:

1 → 2 → 3 → 4 → ... → 10 million

the database can directly locate the partition containing aB72x.


6. Why do we need timestamp / expiry?

Suppose TinyURL keeps links for only 10 years.

Eventually we need to delete expired URLs.

For example:

short_url | created_at | expiry_at
---------------------------------------------
aB72x     | 2020       | 2030
xY72p     | 2025       | 2035
kL92q     | 2026       | 2036

A periodic cleanup job can find expired records.

For example:

DELETE URLs WHERE expiry_at < current_time

But there's an important scaling problem.


7. Why expiry_at needs an index/key

Imagine we have:

1 billion URLs

If expiry_at isn't indexed, the cleanup job may have to scan the entire database:

1 billion records
       ↓
Full table scan
       ↓
Very expensive

Instead, we want efficient access to records based on expiration time.

So we can maintain an appropriate secondary index / sorted access path on expiry_at, depending on the NoSQL database being used.

Conceptually:

Primary key:
short_url

Secondary access path:
expiry_at

Then:

Find URLs where expiry_at < NOW()

is much more efficient.


8. Why NoSQL for TinyURL?

Imagine traffic becomes huge:

1 million requests/sec

A single database server won't handle this comfortably.

With a distributed NoSQL database such as DynamoDB/Cassandra-style systems, data can be distributed across multiple partitions.

Conceptually:

                 NoSQL Cluster
               /      |       \
              /       |        \
        Partition 1 Partition 2 Partition 3
          URLs        URLs        URLs

The database distributes the data automatically according to its partitioning strategy.

This makes horizontal scaling easier.


9. SQL vs NoSQL scaling

With a traditional SQL database:

                MySQL
                  ↓
              One server
                  ↓
            More traffic
                  ↓
             Server load ↑

You can scale vertically:

8 CPU → 32 CPU → 64 CPU

But there are limits.

Eventually you need techniques such as:

  • Read replicas

  • Sharding

  • Partitioning

  • Routing

  • Replication

These can become application/infrastructure complexity.


10. NoSQL horizontal scaling

With a distributed NoSQL database:

              NoSQL
                ↓
       ┌────────┼────────┐
       ↓        ↓        ↓
    Node 1    Node 2    Node 3

As data and traffic grow:

3 nodes
   ↓
10 nodes
   ↓
100 nodes

The database system can distribute partitions across the cluster.

Important interview correction: Don't say "NoSQL automatically solves scaling." Say:

"Many distributed NoSQL databases provide built-in partitioning and replication, which makes horizontal scaling easier."


11. Availability vs Consistency

TinyURL doesn't necessarily require every read to immediately see the latest state.

Suppose:

User creates:
aB72x → URL-A

For a very short period, another replica might not have the latest value.

If the system is designed to tolerate this, eventual consistency can be acceptable.

The priority becomes:

Availability + Scalability

rather than strict consistency everywhere.


12. Hot Partition Problem

This is a very important interview concept.

Suppose this short URL becomes extremely popular:

/aB72x

Imagine:

10 million requests
        ↓
aB72x
        ↓
Same partition

Now one database partition receives disproportionately high traffic.

This is called:

Hot partition

The problem isn't necessarily total database capacity.

The problem is:

One partition is overloaded
while other partitions are underutilized.

13. Cache solves the hot-key problem

This is where Redis comes in.

Instead of:

10 million requests
        ↓
Database
        ↓
aB72x partition

we put a cache in front:

10 million requests
        ↓
       Redis
        ↓
https://example.com/...

The database may only receive a small number of requests.


14. Cache HIT

First request:

GET /aB72x
       ↓
Redis
       ↓
MISS
       ↓
Database
       ↓
Long URL
       ↓
Store in Redis

Next requests:

GET /aB72x
       ↓
Redis
       ↓
HIT
       ↓
Long URL
       ↓
302 Redirect

So:

Database load ↓
Latency ↓
Hot partition pressure ↓

15. Complete TinyURL architecture

                    Client
                      |
                      ↓
                Load Balancer
                      |
                      ↓
               URL Service
                 /       \
                /         \
             Redis       Database
              Cache       NoSQL
                \           /
                 \         /
                  Long URL
                      |
                      ↓
               HTTP 301/302
                      |
                      ↓
              Original Website

Read path

GET /aB72x
     ↓
API Server
     ↓
Redis
   /    \
 HIT    MISS
  ↓       ↓
URL      DB
  ↓       ↓
  ↓    Redis SET
  └───→  ↓
       301/302
          ↓
      Long URL

16. Interview-ready answer

If the interviewer asks:

"How would you design the database for TinyURL?"

A strong answer is:

"First, I derive the schema from the APIs and access patterns. The main read query is resolving a short URL to its long URL, so short_url should be the primary partition key. I would store the long URL along with creation and expiry timestamps. Since TinyURL has simple key-value access patterns, very high scale, and can tolerate eventual consistency in appropriate areas, I would consider a distributed NoSQL database. For expiry cleanup, I need an efficient access path on the expiry timestamp rather than scanning the entire dataset. Finally, I would put Redis in front of the database to cache popular short-to-long URL mappings and prevent hot partitions and excessive database load."

Remember this interview formula

API
 ↓
Queries
 ↓
Schema
 ↓
Partition Key
 ↓
Index
 ↓
Scaling
 ↓
Cache
 ↓
Hot Partition Protection

This API → Query → Schema → Partition Key → Scaling → Cache approach is extremely useful for almost every system-design interview.


TinyURL — Designing the Short URL

Ramesh Choice for production: Approach 2

TinyURL — Short URL Generation: 2 Approaches

ConceptApproach 1: Hash + Collision HandlingApproach 2: Unique ID + Base62
Basic ideaHash the long URL and take a few charactersGenerate a unique numeric ID and encode it
FlowLong URL → Hash → Short CodeUnique ID → Base62 → Short Code
ExampleLong URL → CRC32 → ab5ofd43 → ab5of123456789 → Base62 → 8M0kX
UniquenessNot guaranteed because hashes can collideGuaranteed if the ID is unique
Collision handlingRequiredGenerally not required
Collision solutionChange input: hash(URL + salt) and retryGenerate another unique ID
Database checkCheck whether generated short code already existsUsually unnecessary for collision, but DB uniqueness constraint is still recommended
Short codeDerived from URL contentDerived from unique ID
Same URL twiceCan produce the same short code if same input is hashedCan produce different codes if a new ID is generated each time
Distributed systemCollision handling can become more complicatedNeed a distributed unique-ID generation strategy
ScalabilityGood, but collision probability increases with truncationVery good
Implementation complexityMediumRelatively simple
Main challengeCollision managementDistributed ID generation
Typical production choicePossible, but requires careful designCommon and easier to scale

Approach 1 — Hash

Long URL
   ↓
CRC32 / Hash
   ↓
Take first 5 characters
   ↓
Check DB
   ↓
Collision?
  ├── Yes → Change input/salt → Hash again
  └── No  → Store mapping

Approach 2 — Unique ID + Base62

Generate Unique ID
       ↓
   123456789
       ↓
   Base62 Encode
       ↓
      8M0kX
       ↓
Store:
8M0kX → Long URL

⭐ Interview takeaway

Hash approach: Main problem = collision.
ID + Base62 approach: Main problem = distributed unique ID generation.

For a large-scale TinyURL system, Unique ID + Base62 is generally the cleaner approach because uniqueness is easier to guarantee and the database lookup is naturally shortCode → longURL.

Common Approach — ID + Base62

A very popular production approach is:

Unique ID
   ↓
Base62 Encoding
   ↓
Short Code

Example:

Database ID
123456789
     ↓
Base62
     ↓
8M0kX

This avoids the need to hash the entire long URL.

The fundamental mapping becomes:

123456789
    ↓
8M0kX
    ↓
Database lookup
    ↓
Long URL

This approach is often easier to reason about for uniqueness and scalability.


10. Complete TinyURL Design

                  LONG URL
                     ↓
             Short URL Generator
                     ↓
          ┌──────────────────────┐
          │ Hash / Unique ID     │
          │ + Base62             │
          └──────────────────────┘
                     ↓
                Short Code
                     ↓
                Database
          ┌──────────────────────┐
          │ short → long URL     │
          └──────────────────────┘
                     ↓
               Return Short URL

When the user clicks:

tinyurl.com/8M0kX
        ↓
      Redis
        ↓
   Cache HIT?
    /      \
  YES       NO
   ↓         ↓
Long URL   Database
   ↓         ↓
   └─────────┘
        ↓
   HTTP 301/302
        ↓
    Long URL

No comments:

Post a Comment