Generate keys from a counter, handed to servers in ranges and encoded in base 62, so seven characters cover about 3.5 trillion links with no collisions to check. Store key to URL in a sharded key-value store, serve redirects from a cache in front of it, and record clicks asynchronously so the redirect never waits on analytics.
mediumPattern: Choosing and modeling a databaseFrom Devana's Google question set
Problem
Design a link shortening service. A user submits a long URL and gets a short one back; anyone who opens the short URL is redirected to the long one.
Cover how short keys are generated, the redirect path, click analytics, custom aliases chosen by the user, and optional expiry.
POST /links {url, alias?, expires_at?} -> {key, short_url}
GET /{key} -> 302 redirect to the long URL
GET /links/{key}/stats -> clicks by day, referrer and countryThis is one of Devana's own questions, written in the style of Google's interviews. It is not a question leaked from Google, and Devana is not affiliated with Google.
Requirements
- 10^11 links in total, and 10^5 redirects per second.
- Redirect latency under 20 ms at the 99th percentile.
- Keys of 7 characters or fewer.
- Links may expire, and users may choose custom aliases.
- Worth asking before you start: can a link's target be edited, and how fresh do analytics need to be?
Scenarios
Scenario 1
A user creates a link. Expected: A unique key of at most 7 characters, returned without a call to any central service.
The server encodes the next id from a range it was allocated earlier, so creating a link needs no coordination and no collision check.
Scenario 2
A celebrity shares one link and it takes most of the traffic. Expected: Redirects stay fast.
The key sits in every cache layer, so the database barely sees it. The remaining risk is the one cache node that owns the key, which you handle by replicating hot keys.
Hints
Open one at a time, and try the problem again after each.
Hint 1
Start with arithmetic: how many characters does a base 62 key need for 10^11 links?
Hint 2
Random keys need a uniqueness check on every insert. What gives uniqueness without checking?
Hint 3
Reads vastly outnumber writes. What sits between the redirect service and the database?
Hint 4
Which parts of handling a redirect have to happen before the response, and which can wait?
A worked answer
Requirements and estimates
Redirects dominate the load at 10^5 a second; link creation is far rarer. Assume about 500 bytes per link for the URL, key, owner and timestamps: 10^11 links is then about 50 TB before replication, so the store must be sharded.
Seven base 62 characters give 62^7, about 3.5 × 10^12 keys, comfortably more than 10^11. Six would give about 5.7 × 10^10, which is not enough.
Key generation
Hashing the URL and truncating needs collision handling, and gives the same URL the same key for every user, which breaks per-user analytics. Random keys need a uniqueness check on every insert. A counter is unique by construction: an allocator hands each application server a range of, say, a million ids, and the server encodes each id in base 62.
Sequential keys are guessable. If that matters, pass the id through a reversible permutation before encoding, which keeps uniqueness and hides the order.
Data model and custom aliases
One table: key as the primary key, long URL, owner, created at, expires at. Every read is by key, so a key-value store or a relational table sharded by key both work.
Custom aliases share the key space. Insert them with a uniqueness condition and reject a taken alias. Generated inserts use the same condition; on the rare collision with an alias, the server takes the next id.
The redirect path
Load balancer, a stateless redirect service, a cache from key to URL, and the store on a miss. A cache hit is a memory lookup, which keeps the 99th percentile well inside 20 ms. Expired links are checked on read and removed lazily, with a background sweep for the rest.
Use 302 if every click must reach you for analytics. A 301 lets browsers cache the redirect, which saves traffic and loses repeat clicks.
Analytics off the hot path
The redirect service writes a click event (key, time, referrer, country) to a local buffer or a queue without waiting, then returns. Consumers aggregate counts per key per hour into a separate store. A lost event costs one click in a count; a slow redirect costs every user.
How to say it in the interview
Spend the first minutes on numbers: redirects per second, total links and key length. They drive the design: 62^7 justifies seven characters, the read rate justifies the cache, and the storage size justifies sharding. Then present key generation as a choice among three options, with the reason you picked counters.
The core in code
The piece worth writing in the interview: turning an id from the allocated range into a short key and back. Base 62 uses digits and lowercase and uppercase letters, all safe in a URL.
Python
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
BASE = len(ALPHABET) # 62
def encode(n):
if n == 0:
return ALPHABET[0]
key = []
while n > 0:
n, digit = divmod(n, BASE)
key.append(ALPHABET[digit])
return "".join(reversed(key))
def decode(key):
n = 0
for ch in key:
n = n * BASE + ALPHABET.index(ch)
return n
class IdRange:
"""A block of ids handed to one server by the allocator."""
def __init__(self, start, size):
self.next, self.end = start, start + size
def next_key(self):
if self.next >= self.end:
raise RuntimeError("range used up: fetch a new one")
n, self.next = self.next, self.next + 1
return encode(n)JavaScript
const ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE = ALPHABET.length; // 62
function encode(n) {
if (n === 0) return ALPHABET[0];
let key = "";
while (n > 0) {
key = ALPHABET[n % BASE] + key;
n = Math.floor(n / BASE);
}
return key;
}
function decode(key) {
let n = 0;
for (const ch of key) n = n * BASE + ALPHABET.indexOf(ch);
return n;
}
class IdRange {
// A block of ids handed to one server by the allocator.
constructor(start, size) {
this.next = start;
this.end = start + size;
}
nextKey() {
if (this.next >= this.end) throw new Error("range used up: fetch a new one");
return encode(this.next++);
}
}Both versions are tested against the same cases, including every example on this page.
decodeis for debugging and range checks; the redirect path never needs it, because it looks the key up directly.- JavaScript numbers are exact up to 2^53, far above the 62^7 keys that fit in seven characters, so no BigInt is needed.
- A server that crashes loses the unused part of its range. That wastes ids but never duplicates one, which is the right way to fail.
Follow-up questions
How do you generate keys without a central bottleneck or collisions?
Range allocation: the allocator is called once per million links rather than once per link, so it is never on the hot path, and ranges never overlap, so there is nothing to check.
One link is shared by a celebrity and takes 90% of your traffic. What happens?
It is in every cache layer, so the store barely notices. The pressure lands on the cache node that owns the key; replicate hot keys across cache nodes, or keep a small in-process cache on each redirect server.
How do you record analytics without slowing down the redirect?
Fire and forget: write the click to a buffer or a queue and return. Aggregation happens downstream, and the redirect never waits for it.
How do you stop the service being used to hide malware links?
Check URLs against threat lists at creation and again periodically, rate-limit creation per account and per IP, and send links flagged later to a warning page instead of redirecting.
Common mistakes
- Taking 7 characters of an MD5 hash with no plan for collisions.
- Writing analytics synchronously in the redirect path.
- Skipping the numbers, so the key length and the sharding are never justified.
- Choosing 301 and then promising exact click counts.
Practice it in a mock interview
Reading a solution and explaining one under questioning are different skills. The button below starts this problem as a voice mock interview with Josh, Devana's AI interviewer, with a whiteboard he can read.
Practice this problem in a mock interview
Related problems
- Design an Event and Webhook Platform: An outbox for exactly the committed changes, per-endpoint queues, signed deliveries and replay.
- Consistent Hashing for a Cache Tier: Virtual nodes on a sorted ring; each key belongs to the first position at or after its hash.
- Choosing and modeling a database: the pattern guide, with more practice questions.