Developer

UUIDs Explained: What They Are and When to Use Them

What a UUID actually is, why v4 collisions never happen, when to use one instead of an auto-increment key, and why a UUID is an identifier and not a secret.

Try it now: UUID Generator Generate UUIDs in bulk — free, no signup, runs in your browser.

You are two lines into a new table definition and you hit the primary key. id SERIAL PRIMARY KEY is right there, familiar and fast. But the mobile client needs to create records while offline, and there is talk of merging data from an acquired system next quarter. Suddenly the choice matters.

That is the conversation UUIDs exist for. Here is what they actually are, what the versions mean, and where the real trade-offs sit.

What a UUID is

A UUID (Universally Unique Identifier — Microsoft calls the same thing a GUID) is a 128-bit value. That is all it is at heart: 16 bytes.

You almost always see it written in canonical form: 32 hexadecimal digits split by hyphens into groups of 8-4-4-4-12, for 36 characters total.

f47ac10b-58cc-4372-a567-0e02b2c3d479
             ↑    ↑
       version    variant

Two positions are not random. The first digit of the third group is the version (a 4 above); the first digit of the fourth encodes the variant. The rest carries whatever the version specifies — randomness, a timestamp, or a mixture. Hyphens and letter case are presentation only, and some ecosystems wrap the value in braces.

The point of all this is that anyone, anywhere, can generate one without asking a central authority, and it will not clash with anyone else’s.

The versions that matter in practice

The specification defines several versions. Three are worth your attention.

Version 4 — random

v4 is the everyday default. Set the version and variant bits, fill the remaining 122 bits with random data, done. It leaks nothing about the machine that made it, when it was made, or how many came before it. If someone says “UUID” without qualification, they mean v4.

Version 1 — timestamp plus MAC address

v1 combines a timestamp with the generating machine’s network card address. That makes it roughly sortable and unique per machine, but it leaks information: anyone holding a v1 UUID can read approximately when it was created and identify the hardware that produced it. That fingerprinting risk is why v1 has fallen out of favour for anything user-facing.

Version 7 — time-ordered

v7 is the modern compromise: a 48-bit Unix millisecond timestamp in the high-order bits, then random bits for the rest. You get the unguessability of v4 with roughly increasing values and no MAC address. Why that matters is a database story, covered below — but if you are choosing a scheme for primary keys today, v7 usually beats v4.

Why collisions are not a real concern

The obvious objection to random IDs: what if two come out the same?

Run the arithmetic. A v4 UUID has 122 random bits, giving 2¹²² possible values. By the birthday bound, a 50% chance of a single collision requires generating on the order of 2⁶¹ UUIDs — a few quintillion. At a million per second, that is tens of thousands of years. For any realistic system, a v4 collision is far less likely than undetected disk corruption or a bug in the uniqueness check itself.

One genuine caveat: that assumes a cryptographically secure random source. A UUID built from Math.random() or an embedded device with no entropy at boot loses the guarantee entirely. Use your platform’s proper API — crypto.randomUUID(), not a hand-rolled hex string. The UUID Generator uses the browser’s cryptographic random source for exactly this reason.

UUIDs vs auto-increment integer keys

This is the decision most people actually care about. Both are defensible; the trade-offs are concrete.

Where UUIDs win:

  • Client-side generation. The client can mint an ID before anything touches the database, so an offline app can create records and sync later with no renumbering. With SERIAL you cannot know a row’s ID until the insert returns.
  • Merging across systems. Combining two databases with integer keys means a painful remapping exercise where every foreign key gets rewritten. With UUIDs the rows just merge.
  • Sharding and distributed writes. No shared sequence to coordinate, no single point of contention.
  • They leak nothing. /orders/1043 tells a competitor roughly how many orders you have taken and tells an attacker that /orders/1042 exists. Sequential integers hand over that enumeration for free.

Where integers win:

  • Size. 8 bytes versus 16 — and 36 if you carelessly store the UUID as VARCHAR(36). That multiplies across every index and foreign key in the schema. Use a native uuid type or BINARY(16).
  • Index behaviour. Sequential integers append to the end of a B-tree — cheap, cache-friendly, predictable.
  • Human ergonomics. “Order 1043” is something a support agent can read over the phone. f47ac10b-58cc-4372-a567-0e02b2c3d479 is not.

A pattern that gets you both: keep an internal integer key for joins, and expose a UUID as the public identifier in URLs and APIs.

The index fragmentation problem

This is the strongest technical argument against naive UUID keys.

Databases keep primary key indexes as B-trees in sorted order. Insert sequential integers and every new row lands at the right-hand edge — the same few pages stay hot in memory and writes stay cheap.

Insert random v4 UUIDs and every row lands at an arbitrary point in the tree:

  • Page splits. Inserting into a full page in the middle forces a split, leaving both halves partly empty. The index bloats.
  • Poor cache locality. Writes touch pages scattered across the whole index. Once it outgrows memory, each insert can cost a random disk read.
  • Worse range scans, because logically adjacent rows are physically scattered.

At small scale this is invisible. At tens of millions of rows with a high insert rate, it is the difference between a healthy table and a mysteriously slow one. This is exactly why time-ordered UUIDs exist: v7 puts a timestamp in the leading bits, so new values sort near each other and inserts stay near the end of the index.

UUIDs are identifiers, not secrets

This mistake has the worst consequences, so be clear about it.

A UUID is an identifier. It is not a security token. A well-generated v4 is unguessable in practice, which tempts people into using one as a password reset token, an API key, or a session ID. Resist that, for reasons that have nothing to do with entropy:

  • Identifiers get treated as non-sensitive. They land in application logs, analytics events, error reports, Referer headers, browser history, CDN caches, and support tickets. Every one of those is a leak if the value is secretly a credential.
  • Not every UUID is random. v1 encodes a timestamp and MAC address and is substantially predictable. If any code path swaps generators, a “secret” becomes guessable overnight.
  • No expiry, no revocation, no scope. Real tokens carry those properties; a bare UUID carries none.

When you need a secret, generate a purpose-built one — secrets.token_urlsafe(32) in Python, crypto.randomBytes(32) in Node — or use the Password Generator for a strong one-off value. When you need a stable fingerprint of some content rather than a random label, a hash is the right tool; see the Hash Generator.

Treat UUIDs as things you are happy to see printed in a log file, because eventually they will be.

Generating them

Every mainstream platform ships a generator; you should never write one yourself.

// JavaScript / Node — v4, cryptographically random.
// Browsers require a secure context (HTTPS or localhost).
crypto.randomUUID();
import uuid
uuid.uuid4()        # random v4
str(uuid.uuid4())   # canonical string form
-- PostgreSQL 13+ (built in)
CREATE TABLE orders (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  created_at timestamptz NOT NULL DEFAULT now()
);

-- MySQL 8: UUID() returns v1. Store it compactly and
-- reorder the time fields for better index locality:
SELECT UUID_TO_BIN(UUID(), 1);

For v7 you will generally reach for a library, since built-in support is still arriving across languages and databases.

Quick answers

Can two UUIDs ever be the same? For v4 from a proper random source, the odds are negligible. From a weak or unseeded generator, absolutely — that is the real risk.

Should I use a UUID as a primary key? If you need client-side generation, cross-system merges, or non-enumerable public IDs, yes. Prefer a time-ordered version and store it as uuid or BINARY(16), never VARCHAR(36).

Is a UUID secure enough for a password reset link? No. Use a purpose-built token with expiry and single-use semantics.

v4 or v7? v4 for general-purpose identifiers. v7 when the value is a primary key under heavy inserts.

Why is my UUID column slow? Two usual suspects: it is stored as text instead of a 16-byte type, and random v4 values are fragmenting the index.

The takeaway

A UUID is 128 bits that anyone can generate without coordination — that independence is the entire value proposition. Use v4 by default, reach for v7 when the ID is a primary key under heavy inserts, store it in a binary column rather than a string, and never mistake “hard to guess” for “safe to use as a secret”.

Tools mentioned in this guide

More developer guides

All guides
Developer What Is Base64 Encoding? A Complete Beginner's Guide Base64 turns binary data into safe, printable text. Learn how it works, when to use it, why it is not encryption, and how to encode or decode it in seconds. Developer URL Encoding Explained: Percent-Encoding, Query Strings and Common Bugs Why URLs break on spaces, ampersands and accents — and how percent-encoding fixes it. Covers encodeURI vs encodeURIComponent, + vs %20, and double encoding. Developer MD5 vs SHA-256: Which Hash Function Should You Use? MD5 is fast but broken; SHA-256 is the sensible default. Learn how hash functions work, why hashing is not encryption, and how to hash passwords properly.