Random 5 digit number generator

Uniform five-digit draws over all 100,000 values, for short reference and postal-shaped codes, with leading zeros kept.

G

Where five digits turn up

Five digits is the length of a US ZIP code, of a great many internal reference numbers, and of the short codes used for promotions, batch labels and support tickets. It is long enough to feel like an identifier and short enough to read over the phone without repeating yourself.

A caution about the postal case: a random five-digit string is not a ZIP code. ZIP codes are assigned geographically and only a fraction of the 100,000 five-digit strings correspond to a delivery area, so a generated value will usually address nowhere. Use this to produce reference codes of ZIP shape for test fixtures and layout work, not to produce plausible addresses.

Leading zeros are a data problem, not a display problem

This is the length where the leading-zero question stops being theoretical, because real five-digit codes with leading zeros exist and get destroyed constantly. ZIP codes in New England start with a zero. Paste 01001 into a spreadsheet with a general-format column and it becomes 1001, a different four-character value that no longer matches anything.

The failure is almost never in the generator. It is in the CSV importer, the spreadsheet autodetection, or a JSON round trip through a field typed as a number. Three defences work: format the destination column as text before pasting, quote the field in the CSV and import it explicitly as text, and type the column as CHAR(5) or VARCHAR rather than INT in the database. If a value can begin with a zero, it is a string, and treating it as a number will lose data on the first import.

A generator that draws an integer from 10000 to 99999 produces only 90,000 of the 100,000 five-digit strings. It cannot emit a leading zero at all, which drops the entropy from 16.61 to 16.46 bits and makes the first digit visibly non-uniform.

A random number and a random digit string are different objects

A random number is a value drawn from a numeric range, and its natural representation has no fixed width. A random digit string is a sequence of independently drawn characters, and its width is fixed by construction. The two agree only if the range starts at zero and the string is padded back to width on the way out.

The distinction decides how the value behaves everywhere else in your system. A digit string sorts lexicographically in the order you expect, because every value has the same length. A number stored as text and left unpadded does not: 9999 sorts after 10000. A digit string survives a JSON round trip. A number may not, once it grows past the range a double can hold exactly.

For anything that is an identifier rather than a quantity, choose the string. Nothing downstream ever needs to add two reference codes together, and typing them as numbers only invites the tooling to reformat them.

How many codes before two collide

A five-digit space holds 100,000 values and carries 5 x log2(10) = 16.61 bits. Independent draws collide far sooner than intuition suggests. The chance of at least one duplicate reaches 1% at 45 codes and 50% at 373.

So a promotion issuing 500 randomly generated five-digit codes will more likely than not issue the same code twice. If codes must be distinct, either draw without replacement, or enforce a unique constraint in the database and retry on conflict. Do not rely on the space being large; at five digits it is not.

Doing this in your own code

JavaScript
// 5 independent digits, uniform, leading zeros preserved.
// 250 is Math.floor(256 / 10) * 10. Bytes at or above it are thrown away,
// which is what keeps every digit at exactly 1 in 10.
function randomDigits(n) {
  const buf = new Uint8Array(1);
  let out = "";
  while (out.length < n) {
    crypto.getRandomValues(buf);
    if (buf[0] >= 250) continue;
    out += buf[0] % 10;
  }
  return out;
}

randomDigits(5);   // a 5-character string, never a number

The usual shortcut, Math.floor(Math.random() * 9e5) + 1e5, cannot produce a leading zero and draws from a PRNG whose state is recoverable from its output.

Python 3.6+
import secrets

# Draw the digits. This never becomes an int, so leading zeros survive.
"".join(secrets.choice("0123456789") for _ in range(5))

# Or draw one integer and pad it back to width.
f"{secrets.randbelow(10 ** 5):05d}"

random.randint() uses the Mersenne Twister and is not a security boundary. secrets draws from os.urandom.

Go
import (
    "crypto/rand"
    "fmt"
    "math/big"
)

limit := new(big.Int).Exp(big.NewInt(10), big.NewInt(5), nil)
v, err := rand.Int(rand.Reader, limit)
if err != nil {
    panic(err)
}

// The width verb is the part that keeps the leading zeros.
fmt.Printf("%05d\n", v)
Java
import java.security.SecureRandom;

SecureRandom rnd = new SecureRandom();
StringBuilder sb = new StringBuilder(5);
for (int i = 0; i < 5; i++) {
    sb.append(rnd.nextInt(10));
}
String value = sb.toString();

nextInt(bound) already rejects the unusable top of the range, so it is unbiased for a bound of 10.

PHP 7+
// random_int() is backed by the system CSPRNG.
// rand() and mt_rand() are not, and must not be used here.
$value = str_pad((string) random_int(0, 10 ** 5 - 1), 5, '0', STR_PAD_LEFT);
Shell
# 5 digits straight from the kernel CSPRNG.
# tr keeps only ASCII digits, so every byte that survives is uniform over 0-9.
LC_ALL=C tr -dc '0-9' < /dev/urandom | head -c 5; echo

Questions

Can I use these as ZIP codes?

Only as ZIP-shaped test data. ZIP codes are assigned to real geography and most five-digit strings are not assigned to anything, so a generated value will usually correspond to no delivery area. For fixtures that must pass address validation, use a published list of real ZIP codes instead.

Why does my spreadsheet delete the leading zero?

Because it is guessing that the column is numeric and 01001 is the number 1001. Format the column as text before pasting, or import the CSV with the column type set to text. The zero is present in the generated output; it is the import that removes it.

How many 5-digit codes can I issue safely?

The chance of a duplicate hits 1% at about 45 codes and 50% at about 373. There is no batch size at which collisions become unlikely by accident, so add a uniqueness check or move to more digits.

Is the full 00000 to 99999 range covered?

Yes. Each of the five digits is drawn independently and uniformly, so all 100,000 strings including 00000 appear at the same rate. Generators that draw an integer from 10000 upward cover only 90,000 of them.

How much entropy is a 5-digit code?

Exactly 5 x log2(10) = 16.61 bits. That is well below any threshold at which a secret can stand on its own, so treat five-digit values as identifiers, not credentials.

Related generators