UUID v4 Explained: Format, Versions & When to Use
Table of Contents
You would need to generate about 1 billion UUIDs every second for roughly 85 years before you'd have even a 50% chance of a single collision. Let that number sit for a moment: a billion per second, for 85 years, just to reach a coin flip on one duplicate. That absurd improbability is the entire reason UUIDs exist — identifiers so random that you can create them anywhere, on any machine, with no coordination, and still treat them as unique.
And yet developers argue about UUIDs constantly: are they better than auto-incrementing IDs? Which version should you use? Do they hurt database performance? Most of the arguments come from mixing up the versions or applying UUIDs where a simple integer would do. Let's sort it out properly.
The 8-4-4-4-12 Shape, Decoded
A UUID looks like `f47ac10b-58cc-4372-a567-0e02b2c3d479` — 32 hexadecimal characters arranged in groups of 8-4-4-4-12, joined by dashes. Those dashes are cosmetic; strip them and you have 32 hex digits, which is 128 bits of identifier. The total address space is 2^128, or about 340 undecillion — a number so large that "astronomical" undersells it.
But a version 4 UUID isn't 128 bits of pure randomness. Six bits are reserved for metadata: four bits mark the version (`0100`, binary for 4, which is why the third group's first digit is always `4`), and two to three bits mark the variant (which is why the fourth group's first digit is always `8`, `9`, `a`, or `b`). That leaves 122 bits of actual randomness — still vastly more than you'll ever need, as the collision math above shows.
The fixed positions are genuinely useful: if you see a UUID whose third group starts with `4`, you know it's a v4 without being told. If it starts with `1`, it's a v1 (time-based). This self-describing property means UUIDs carry their own provenance, which matters when you're debugging a system that mints several kinds.
The Versions: v1, v4, v7, and When Each Wins
Version 4 — random — is the default people mean when they say "UUID," and it's the right choice when you need an identifier with no information leakage and no coordination. Generate it anywhere, use it immediately.
Version 1 encodes a timestamp plus the machine's MAC address. The timestamp makes v1 UUIDs roughly time-sortable (handy for database indexes), but the MAC address leaks hardware identity — a genuine privacy concern that got v1 a bad reputation. You can generate v1 with a random node ID instead of the MAC, but at that point most people just move on to v7.
Version 7 is the modern answer: Unix timestamp in milliseconds plus randomness, designed to be time-ordered and privacy-safe. If you're choosing UUIDs for database primary keys in 2026, v7 deserves your attention — it keeps the global uniqueness while playing nicely with B-tree indexes, which v4's pure randomness does not (more on that below). Versions 2, 3, and 5 exist too — v3 and v5 are deterministic hashes of a namespace and name, useful when you need the same input to always produce the same UUID — but v4 and v7 cover nearly all real-world needs.
For bulk test data, fixtures, or one-off identifiers, just generate what you need: the UUID / GUID (v4) Bulk Generator Online mints v4 UUIDs in bulk, formatted with or without dashes, right in your browser.
UUID vs Auto-Increment: The Honest Comparison
This is the debate that never dies, so here's the honest version with actual winners per situation instead of a cop-out.
Auto-increment integers win on: storage (4 bytes vs 16), index performance (sequential inserts go to the same B-tree page; random UUIDs scatter writes across the index, causing fragmentation and slower inserts at scale), readability (`order 1042` vs `order f47ac10b-...` in a support call), and sortability (creation order for free). For a single database with a single writer, auto-increment is simply better engineering — smaller, faster, friendlier.
UUIDs win on: distributed systems (two services can mint IDs independently with no coordination and no central sequence to become a bottleneck or single point of failure), security through unpredictability (`/users/1043` is enumerable — an attacker can crawl your user list; `/users/f47ac10b-...` is not), offline-first apps (create records on a phone with no connection, sync later, no ID conflicts), and database merges (combining two databases with auto-increment IDs is a renumbering nightmare; with UUIDs it's a non-event).
The performance caveat is real but specific. Random v4 UUIDs as clustered primary keys in MySQL/InnoDB do fragment indexes and slow high-volume inserts — this is measured, not theoretical. But it matters at write-heavy scale (thousands of inserts per second), not for the average CRUD app. And v7 largely fixes it by being time-ordered. The mistake is cargo-culting either choice: using UUIDs "for scale" in an app that will never exceed 10,000 rows, or using auto-increment in a distributed system and then bolting on a coordination service later.
My rule: single database, single writer, internal IDs → auto-increment. Distributed writers, public-facing IDs, offline creation, or any merge scenario → UUID (v7 for primary keys, v4 everywhere else).
One hybrid worth knowing: some teams use auto-increment internally and expose UUIDs (or opaque tokens) publicly — a mapping table translates between them. You keep the database performance and readability of integers while outsiders only ever see unguessable identifiers. It's more moving parts, but for user-facing resources at scale it's often the best of both worlds.
When NOT to Use UUIDs
A few cases where UUIDs actively hurt: URLs that humans read or share (a 36-character ID in a shared link is hostile — consider a short unique slug instead), high-frequency analytics event IDs where 16 bytes per row across billions of rows is real storage money, and anywhere you need meaningful ordering (UUIDs sort randomly; a timestamp or sequence sorts naturally).
There's also the debugging tax. Scanning logs for `order 1042` is easy; scanning for `order f47ac10b-58cc-4372-a567-0e02b2c3d479` is not — you end up copying and pasting IDs for every lookup, and telling two UUIDs apart at a glance is hopeless. Teams that switch to UUIDs often adopt a compromise: UUID as the primary key, plus a short human-friendly reference (like `ORD-1042`) generated alongside it for support tickets, logs, and conversation. You get the distributed-systems benefits where they matter (the key) and readability where humans are involved (the reference).
Also: don't use a UUID as a secret. UUIDs are unique, not unguessable-by-design — v4's 122 random bits are practically unguessable, but that's a side effect, not a security property. For tokens and secrets, use your platform's cryptographic random generator explicitly and say what you mean.
Storing UUIDs Efficiently in Databases
If you've committed to UUIDs, store them well — the naive approach wastes space and speed. The worst option is `CHAR(36)`: 36 bytes per ID plus collation overhead, and string comparison on every join. Better is `BINARY(16)` in MySQL (16 bytes, the raw 128 bits) — though you'll want helper functions to convert between the binary and the dashed text form, since debugging raw binary is miserable.
Postgres users have it easiest: the native `uuid` type stores 16 bytes, validates the format on write, and renders the familiar dashed string on read — no conversion code needed. SQL Server's `uniqueidentifier` is the equivalent. The general principle: store the 16 raw bytes, display the 36-character string, and never make the database compare strings when it could compare bytes. And whatever the type, think about index clustering: with v4's random ordering, consider a non-clustered primary key plus a clustered index on creation time if your queries are time-range heavy — you get UUID uniqueness without the random-insert fragmentation penalty.
Try it free — no sign-up
Run the numbers yourself in seconds. Everything happens in your browser.
Open the UUID / GUID (v4) Bulk Generator OnlineFrequently Asked Questions
What does the 8-4-4-4-12 format mean?
It's 32 hexadecimal digits (128 bits) grouped as 8-4-4-4-12 with dashes for readability. In a v4 UUID, the third group's first digit is always `4` (the version marker) and the fourth group's first digit is always `8`, `9`, `a`, or `b` (the variant marker) — the remaining 122 bits are random.
What are the odds of a UUID v4 collision?
Effectively zero for any real system. You'd need to generate around 2.7 quintillion UUIDs to reach a 50% collision chance (the birthday-paradox bound at 122 random bits). Generating a billion per second for 85 years gets you to that coin flip. Your application will never come close.
Should I use UUID or auto-increment for my primary key?
Auto-increment if you have a single database with a single writer — it's smaller, faster to index, and human-readable. UUID if you have distributed writers, need non-enumerable public IDs, support offline record creation, or will ever merge databases. For UUID primary keys at write-heavy scale, prefer time-ordered v7 over random v4 to avoid index fragmentation.
What is the difference between UUID v4 and v7?
V4 is 122 bits of pure randomness — maximum unpredictability, but random ordering that fragments database indexes. V7 embeds a millisecond timestamp plus randomness — still globally unique, but time-sortable, which keeps B-tree indexes efficient. Use v4 for general identifiers and tokens; v7 for database primary keys.
Are UUIDs safe to expose in URLs?
V4 UUIDs are safe from enumeration — unlike sequential IDs, they can't be guessed or crawled in order. That's a genuine security benefit for user-facing resources. The tradeoff is readability: a 36-character ID is ugly in a shared link, so many apps expose a short slug publicly while keeping the UUID internal.
Why do some UUIDs start with the same characters?
Because they're v1 or v7, which embed timestamps — UUIDs generated close together in time share a prefix. That's by design (it's what makes them sortable). V4 UUIDs, being random, share prefixes only by coincidence. If your "random" UUIDs show time-correlated prefixes, something is generating v1/v7 when you expected v4.