URL shortener codes: an interactive birthday problem for random, hashed and counter IDs
A URL shortener needs a short, unique code for every link. There are three common ways to make one: draw random characters, hash the URL and keep the first few characters, or take the next number from a counter and write it in base62. Random and hashed codes can collide, and the birthday problem says they collide far sooner than intuition suggests. Counters never collide but need coordination and produce guessable codes.
The playground draws random two-character codes into a grid of all 3,844 possibilities, then applies the same arithmetic to real code lengths and volumes.
Two-character codes allow 3,844 values. After 73 random codes, 1 landed on a code already taken, the first after only 40: the chance of at least one collision is 49.5%, while the chance that the next code collides is 1.9%. At 7 characters there are 3.5 trillion codes. Collisions become more likely than not after only 2.2 million of them, and 1 billion links will produce about 142,000 collisions, yet any single new code collides with probability 0.0284%: generate, check, and retry the rare clash.
lS2ZJtq0L5iCyolzQe9V7lD4- Possible codes
- 3.5 trillion
- Even odds of a collision after
- 2.2 million
- Collisions to expect
- 142,000
- Chance the next code collides
- 0.0284%
The birthday problem, in codes
With 3,844 possible codes, drawing just 73 at random gives even odds that two match, and in the playground the first collision comes at the 40th code. The same holds at scale: 7 base62 characters give 3.5 trillion codes, but collisions become more likely than not after only 2.2 million links, and a billion links produce about 142,000 of them. That sounds alarming until you look at the other number: when a billion codes are taken, a new random code collides with probability 0.028%. So random codes work, provided every insert checks for a clash and retries, which a unique index does atomically.
Three ways to make codes
- Random codes with a unique index and a retry are simple and unguessable. The database does the collision check, and retries stay rare as long as the space is far larger than the number of links.
- Hashing the URL, for example the first 7 characters of a base62-encoded SHA-256, gives the same code for the same URL, which deduplicates for free. Different URLs still collide at the same birthday rate, so a clash needs a fallback such as rehashing with a salt.
- A counter in base62 cannot collide: a billion links need only 6 characters. But the counter must be shared, through a ticket server, ranges of ids handed to each server, or Snowflake-style ids, and sequential codes let anyone enumerate every link. Scrambling the number with a reversible permutation keeps uniqueness while hiding the order.
A key generation service combines the ideas: it pre-generates unused random codes in bulk and hands batches to application servers, so creating a link needs no collision check at all.
Random codes with a retry, and base62
import secrets, string
ALPHABET = string.digits + string.ascii_lowercase + string.ascii_uppercase # base62
def random_code(length=7):
return "".join(secrets.choice(ALPHABET) for _ in range(length))
def shorten(db, url, tries=5):
for _ in range(tries):
code = random_code()
# the unique index on code makes the collision check atomic
inserted = db.execute(
"INSERT INTO links (code, url) VALUES (%s, %s) ON CONFLICT (code) DO NOTHING",
(code, url)).rowcount
if inserted:
return code
raise RuntimeError("no free code after retries")
def base62(n):
"""For counter-based ids: unique by construction, no check needed."""
s = ""
while True:
n, r = divmod(n, 62)
s = ALPHABET[r] + s
if n == 0:
return sIn a system design interview
Do the arithmetic out loud: “100 million new links a month is about 6 billion over five years. Seven base62 characters give 3.5 trillion codes, so a random code collides with an existing one less than 0.2% of the time even at the end. We generate randomly, insert under a unique index, and retry on conflict. Codes are unguessable, which matters because some links are private.” Expect the follow-ups: custom aliases, the same long URL shortened twice, and how the check works when the table is sharded by code.
Read the guide to hashing and encoding or unique ids, or watch a strong candidate design a URL shortener.
Questions people ask
How long should URL shortener codes be?
Long enough that the code space dwarfs the number of links. Seven base62 characters give about 3.5 trillion codes, so even billions of links leave a new random code a small chance of colliding. Counter-based codes can be shorter: a billion links fit in six characters.
What is the birthday problem?
The surprising result that among random draws from a large space, a repeat appears after only about the square root of the space size. With 365 birthdays, 23 people give even odds of a shared birthday; with 3.5 trillion short codes, about 2.2 million random codes give even odds of a collision.
How do you handle short code collisions?
Insert the new code under a unique index and, if the insert conflicts, generate another and retry. Because a new code collides with probability equal to the share of codes already taken, retries stay rare even when collisions somewhere are certain.
Should a URL shortener hash the URL or use random codes?
Hashing gives the same code for the same URL, which deduplicates repeated links, but truncated hashes of different URLs collide at the same rate as random codes, so a fallback is still needed. Random codes are simpler and unguessable; many designs use random codes and a separate lookup for duplicate URLs if deduplication matters.
What about using a counter and base62?
Encoding an auto-incrementing id in base62 guarantees unique, short codes with no collision check. The costs are coordinating the counter across servers, through a ticket server, id ranges or Snowflake-style ids, and codes that are sequential, so anyone can enumerate links unless the ids are scrambled.
What is a key generation service?
A service that pre-generates unused random codes and hands them to application servers in batches. Creating a link then just takes the next code from the local batch, with no collision check, at the cost of running and coordinating the service and losing a few unused codes if a server dies.
What is base62 encoding?
Writing a number in base 62 using the digits 0 to 9 and the letters a to z and A to Z, which are all safe in URLs. Each character holds about 5.95 bits, so 7 characters cover numbers up to about 3.5 trillion.