UUID/GUID Versions Explained
"UUID" almost always means "v4, fully random" in practice — but that default is quietly expensive at scale, and as of RFC 9562 there's now a standardized alternative built for exactly this problem. Here's what each version actually encodes, and a byte-order quirk specific to .NET that breaks interop if you don't know it's there.
// the versions
4 here means v4 (random). The leading bits of the fourth group encode the RFC variant, normally one of 8/9/a/b.Guid.NewGuid() and crypto.randomUUID() produce00000000-0000-0000-0000-000000000000) and Max (all fs) are also formally reserved values as of RFC 9562 — useful as an explicit "no value"/"unbounded" sentinel instead of null, in systems where the column type disallows nulls.// why v7 exists: random UUIDs quietly wreck index performance
A v4 UUID as a primary key lands at a random position in a B-tree index every single insert. On a small table that's invisible. At millions of rows, every insert forces a random page read, and pages that fill up have to split — over time the index fills with half-empty pages, fragments badly, and range scans degrade into random I/O instead of sequential reads. v7 fixes this by putting a 48-bit Unix millisecond timestamp — see Unix Timestamp & Epoch Time Explained for what that actually counts from — directly in the UUID's first 6 bytes, so values generated later always sort after values generated earlier and inserts land at the end of the index instead of scattering across it.
v7's timestamp-first layout means new rows' keys cluster at the end of the index instead of scattering everywhere, which is exactly what a B-tree wants for insert-heavy workloads. If you're choosing a UUID primary key for a new table today, v7 is very likely the better default over v4 — you keep the "no coordination needed to avoid collisions" property of UUIDs while getting most of the locality benefit of an auto-incrementing integer.
// name-based UUIDs (v3/v5) — when determinism is the point
Unlike v4/v7, v3 and v5 aren't random — they're a hash of a namespace UUID plus a name string, so the same two inputs always produce the same UUID, anywhere, with no shared state or database lookup required. Useful for deriving a stable ID from something you already have — e.g. turning a known external key (an email, a DNS name, a legacy string ID) into a UUID-shaped identifier deterministically, so the same input never produces two different IDs across services.
// "GUID" vs "UUID" — and the .NET byte-order trap
GUID is Microsoft's name for the same 128-bit format — modern Guid.NewGuid() produces a standard RFC-compliant v4 UUID, so the terms are interchangeable in practice today. The string representation matches the RFC exactly. Where it diverges is the raw bytes.
Guid.ToByteArray() does not return RFC byte order. .NET stores a Guid's first three groups (Data1, Data2, Data3) in little-endian order internally, while the RFC specifies big-endian (network byte order) throughout. The string form looks identical either way — the mismatch only appears once you serialize to raw bytes and compare against a non-.NET system (Java, Postgres's native uuid type, a byte-level API contract). .NET 8 added ToByteArray(bigEndian: true) specifically to produce RFC-correct bytes — worth using explicitly any time a GUID's byte representation crosses a system boundary, not just its string form.// frequently asked questions
What's the difference between UUID v4 and v7?
v4 is fully random — what Guid.NewGuid() and crypto.randomUUID() produce by default. v7 puts a 48-bit Unix millisecond timestamp in the first 6 bytes, making values sortable by creation time while keeping the rest random. v7 is recommended for new systems specifically because it fixes a real database index performance problem v4 causes.
Why does using v4 UUIDs as a primary key hurt database performance?
A v4 UUID lands at a random position in a B-tree index on every insert. At millions of rows, every insert forces a random page read, pages fragment badly, and range scans degrade into random I/O. Published benchmarks show v4 primary keys costing 30–50% more insert throughput than a sequential key, with roughly 25% larger index size from the fragmentation.
Are "GUID" and "UUID" the same thing?
In practice, yes — GUID is Microsoft's name for the same 128-bit format, and modern Guid.NewGuid() produces a standard RFC-compliant v4 UUID. The string representation matches the RFC exactly. Where they diverge is the raw bytes.
Why doesn't Guid.ToByteArray() match the RFC byte order?
.NET stores a Guid's first three groups internally in little-endian order, while the RFC specifies big-endian throughout. The string form looks identical either way, but the mismatch appears once you serialize to raw bytes and compare against a non-.NET system like Java or Postgres's native uuid type. .NET 8 added ToByteArray(bigEndian: true) specifically to produce RFC-correct bytes.
When should I use a name-based UUID (v3/v5) instead of random?
When you need the same input to always produce the same UUID, with no shared state or database lookup — v3 (MD5) and v5 (SHA-1) hash a namespace UUID plus a name string deterministically. Useful for turning a known external key, like an email or a legacy string ID, into a UUID-shaped identifier that's stable across services.
// try it yourself
Generate a random (v4) GUID, or paste one you already have, and see it in every format .NET's Guid.ToString() supports — N, D, B, P, and the X constructor form — with the GUID Generator & Format Converter.
A UUID is 128 bits. Four bits of that encode a version number that tells you how the rest was generated — visible as the first character of the third group, e.g. the
4inxxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. RFC 9562 (May 2024) is the current spec — it obsoletes RFC 4122 but doesn't invalidate anything: every v1/v3/v4/v5 UUID ever generated is still fully valid.