Definition
- URL shortener : a service that maps a long destination URL to a short unique key, stores the mapping durably, and redirects requests for the short key to the original URL.
- Base62 encoding : encoding a numeric id using 62 characters (digits + lowercase + uppercase) to produce a short, URL-safe string.
- Cache-aside : check the cache first on redirect; on miss, load from the database and populate the cache.
- Custom alias : a user-chosen key like "nbi-salary" that must be checked for uniqueness before storing.
- Expiration : a TTL on the mapping after which the short link stops working.
National Bank of India sends customers long, ugly links to their statements. Instead the bank issues a short code like "nbi.in/a9Xk2" printed on the SMS. When Rajesh taps it, the bank looks up that short code in a directory and sends him to the real link. The short code is like a locker number: small and easy to carry, while the actual contents are stored in the locker room.
Understanding the Concepts
What is a URL shortener?
A URL shortener maps a long URL to a short unique key, stores that mapping durably, and redirects any request for the short key back to the original URL. It is one of the most commonly asked system design interview questions because it touches key-value storage, caching, encoding, and scale.
How do you generate the short key?
The cleanest approach is to assign each new URL a unique numeric id from a counter or database sequence, then base62-encode that number. Seven base62 characters cover over 3.5 trillion values.
package com.bank.urlshortener;
public class Base62Encoder {
private static final String ALPHABET =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public String encode(long id) {
if (id == 0) return "0";
StringBuilder sb = new StringBuilder();
while (id > 0) {
sb.append(ALPHABET.charAt((int) (id % 62)));
id /= 62;
}
return sb.reverse().toString();
}
}
Why is caching critical?
A link is created once but clicked thousands of times, making the system extremely read-heavy. A cache like Redis in front of the database serves redirects in single-digit milliseconds.
package com.bank.urlshortener;
public class UrlResolver {
public String resolve(String shortKey) {
String url = cache.get(shortKey); // Redis GET
if (url == null) {
url = repository.findByKey(shortKey); // DB lookup
if (url != null) cache.put(shortKey, url); // populate cache
}
return url;
}
}
How do you handle custom aliases?
A user requests a specific key like "nbi-salary" and you must check it is not already taken. Use a unique constraint in the database; if the insert fails, the alias is taken.
How do you handle expiration?
Store a TTL with the mapping. On resolve, check if the link has expired. A background job periodically cleans up expired entries.
How do you track analytics?
Record each click asynchronously via a message queue so counting never slows the redirect. A consumer aggregates click counts per link.
package com.bank.urlshortener;
public class ClickTracker {
public void trackClick(String shortKey) {
analyticsQueue.send(new ClickEvent(shortKey, Instant.now()));
// Consumer aggregates counts; redirect is not slowed
}
}
What is the data model?
A simple mapping from short key to long URL, plus metadata: creation time, expiry, owner, and click counter.
package com.bank.urlshortener;
public record UrlMapping(
String shortKey,
String longUrl,
String owner,
Instant createdAt,
Instant expiresAt,
long clickCount
) {}
What about consistency?
A brand-new link might not be in every cache immediately, but eventual consistency on a redirect is acceptable because, unlike money, a short link appearing a second late harms nobody.
What should a senior engineer know?
A senior engineer knows to handle the rare collision case when using hash-based key generation, considers distributed id generation (Snowflake) for multi-region, and monitors cache hit ratios to ensure the cache is effective.
Visual Overview
URL Shortener Architecture
Create:
┌──────────┐ POST /shorten ┌──────────────┐
│ Client │──────────────────►│ API Server │
└──────────┘ └──────┬───────┘
│ generate id
│ base62 encode
│ store mapping
▼
┌──────────────┐
│ Database │
│ key -> URL │
└──────────────┘
Redirect:
┌──────────┐ GET /a9Xk2 ┌──────────┐ HIT ┌───────┐
│ Client │─────────────►│ Cache │──────►│ 302 │
└──────────┘ │ (Redis) │ │Redirect│
└────┬─────┘ └───────┘
MISS │
┌────▼─────┐
│ Database │── populate cache
└──────────┘
Analytics:
Redirect ──► async event ──► Kafka ──► Counter Service
Key Takeaway
A URL shortener maps a long URL to a short base62 key, stores the mapping durably, and redirects fast. The key design decision is generating collision-free short keys by base62-encoding a unique numeric id from a database sequence. Seven characters cover trillions of values.
The system is extremely read-heavy, so I put Redis in front with cache-aside. On redirect I check the cache first, only hitting the database on a miss. Custom aliases require a uniqueness check, expiration uses a TTL, and analytics are recorded asynchronously so counting never slows the redirect.
At National Bank of India, this shortened statement links for SMS delivery. The trade-off I always call out is consistency: a new link appearing a second late is fine, unlike money. Being explicit about where I can relax consistency is what keeps the redirect path fast.
Remember one sentence: generate collision-free base62 keys from a sequence, cache redirects in Redis because reads vastly outnumber writes, and track analytics asynchronously.
---