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'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.// 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.