Guide

Every short link needs a code that no other link uses. Codes are short, so collisions and guessability both matter. The usual answer is to encode a number in base62, which packs many possible values into few characters. Seven characters is enough for trillions of links.

Concept

Before you start

  • Base62: a way to write numbers with 62 symbols (0–9, a–z, A–Z) instead of 10. More symbols means fewer characters for the same number.

Length math

LengthCapacity
6 characters62⁶ ≈ 56.8 billion
7 characters62⁷ ≈ 3.5 trillion

Seven characters gives over 500 times the room needed for 6 billion links. Choosing 7 up front means you never have to change public URLs as the data grows.

The big idea

Each code comes from one of three approaches. Each one answers two questions: can two links get the same code, and can someone guess the next one?

ApproachHow it worksCollisionsGuessable?Coordination
Hash the URLHash the long URL, base62-encode it, keep 7 charactersPossible, so check and retryNoA uniqueness check on every create
Counter and base62Hand out increasing numbers and encode themNoneYes, unless you shuffleLease blocks of numbers
Key servicePre-generate random codes in batchesNone, if issued codes are recordedNoBatches handed out ahead of time

Hashing gives the same code for the same URL, which helps deduplication. The cost is a uniqueness check on every create.

Counters are the simplest collision-free option. Each app server leases a block of 10,000 numbers, so the coordinator is touched once per 10,000 creates.

Key services produce random codes that are hard to guess, at the cost of an extra component that must record which codes were issued.

Walking through base62

Encoding repeatedly takes the remainder by 62 and divides by 62. Here is the function from the source, with a few values:

TypeScript
const ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

function toBase62(n: bigint): string {
  if (n === 0n) return ALPHABET[0]
  let out = ""
  while (n > 0n) {
    out = ALPHABET[Number(n % 62n)] + out
    n /= 62n
  }
  return out.padStart(7, ALPHABET[0])
}

console.log(toBase62(1n), toBase62(125n), toBase62(3_500_000_000_000n))

Output:

0000001 0000021 ZCpdjxe

For 125, the steps are 125 % 62 = 1, then 2 % 62 = 2. Reading the remainders from last to first gives 21, and padding makes it 0000021. The counter 3.5 trillion fits in 7 characters.

Common mistakes

  • Using the raw counter as the public code. Codes such as 0000001 and 0000002 let anyone walk through other people's links. Shuffle the numbers before encoding.
  • Checking for a duplicate, then inserting. Two requests can both pass the check. Put a unique constraint on code and retry on conflict.
  • Reusing a leased block after a crash. Record which block each server owns. Never hand out a block that was not acknowledged as used.
  • Choosing 6 characters to save space. You gain little and must redesign URLs as the corpus grows.

Check yourself

1. Why is a 7-character base62 code enough for 6 billion links?

Answer: 62⁷ is about 3.5 trillion, roughly 587 times 6 billion.

2. Why shuffle a counter before encoding it?

Answer: Sequential codes can be walked in order, which exposes links that were meant to be private.

Key takeaways

  • Base62 turns a number into a short code, and seven characters covers trillions.
  • A counter with leased blocks is simple, collision-free and the usual interview pick.
  • Hashing the URL gives deduplication but needs a uniqueness check on every create.
  • Shuffle or randomize codes when links are meant to stay hard to find.

Further learning

Next, see how clicks get counted without slowing redirects in click counting off the hot path.