Developer & Data

GUID vs UUID: What's the Difference?

GUID and UUID name the same 128-bit identifier, but Microsoft serializes the first three fields in the opposite byte order. Here is where that breaks, with the exact bytes.

Feb 22, 2026Updated Jul 28, 20269 min readBy Mateo Díaz
uuiddevelopersdatabases
TLDR
  • GUID and UUID describe the same 128-bit value, and Microsoft's GUID is simply its name for what RFC 9562 calls a UUID.
  • The real difference is byte order: Microsoft stores the first three fields little-endian, so the same 128 bits print as two different strings.
  • RFC 9562 replaced RFC 4122 in 2024 and added UUIDv7, which is time-ordered and a better default than v4 for database keys.

Use it now

UUID Generator

Open tool
Two grouped hexadecimal identifier rows showing the first byte groups reversed while the rest stay aligned

Here's a string: 6ba7b810-9dad-11d1-80b4-00c04fd430c8. Ask ten developers whether that's a GUID or a UUID and you'll get ten confident, contradictory answers. The honest one is: doesn't matter, it's the same 128 bits either way, just two different names slapped on by two different institutions.

GUID is Microsoft's term. UUID is what the actual standard calls it. Same size, 128 bits, same string shape once you write it out — 32 hex digits split into five groups by hyphens. If somebody hands you a "GUID" and your code wants a "UUID," hand back the exact same string. No conversion needed. As text, they're interchangeable, full stop.

Except that's the part everyone stops at, and it's also where the interesting bug lives. As raw bytes, not as text, Microsoft's version and the RFC version store the same value in a different order. Invisible in the string. Not invisible once something starts reading those bytes off disk.

Same value, wearing two labels

6ba7b810-9dad-11d1-80b4-00c04fd430c8
└──────┘ └──┘ └──┘ └──┘ └──────────┘
   4b     2b   2b   2b       6b        = 16 bytes total
UUIDGUID
Full nameUniversally Unique IdentifierGlobally Unique Identifier
SpecRFC 9562 (2024), formerly RFC 4122Microsoft's own MS-DTYP doc
Size128 bitsSame
Usually writtenlowercase, no bracesuppercase-ish, often wrapped like {6ba7b810-…}
You'll find it inbasically everywhere.NET, COM, SQL Server, deep in the Windows registry

That covers the part every other page on this topic covers. Now the part that actually causes production incidents.

The byte-order trap

Microsoft's GUID is, under the hood, a C struct:

typedef struct _GUID {
  unsigned long  Data1;    // 4 bytes
  unsigned short Data2;    // 2 bytes
  unsigned short Data3;    // 2 bytes
  unsigned char  Data4[8]; // 8 bytes
} GUID;

Data1 through Data3 are integers, and on x86 and ARM, integers get stored little-endian — smallest byte first. Data4 is just a byte array, stored exactly as written, no reordering.

RFC 9562 says the whole 16 bytes should be big-endian, network order, the same convention basically everything outside Windows uses.

Take that DNS namespace UUID from the RFC and lay both encodings side by side:

UUID string     : 6ba7b810-9dad-11d1-80b4-00c04fd430c8

RFC 9562 bytes  : 6b a7 b8 10  9d ad  11 d1  80 b4  00 c0 4f d4 30 c8
Microsoft bytes : 10 b8 a7 6b  ad 9d  d1 11  80 b4  00 c0 4f d4 30 c8
                  └─reversed─┘ └rev┘  └rev┘  └──── identical ────┘

First three groups, flipped. Last two, untouched, because Data4's a byte array and there's nothing to flip.

And here's the actual danger in it. Read Microsoft's byte layout using RFC rules, and nothing errors out. You just quietly get a different, perfectly well-formed UUID back:

6ba7b810-9dad-11d1-80b4-00c04fd430c8   ← what you started with
10b8a76b-ad9d-d111-80b4-00c04fd430c8   ← what comes back out

No exception. Nothing looks broken. It's just wrong now, and it stays wrong until somebody's chasing a foreign key that points at absolutely nothing three weeks later, wondering what happened.

You can watch this happen yourself, three lines of Python:

import uuid
u = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
u.bytes      # b'k\xa7\xb8\x10\x9d\xad\x11\xd1...'  RFC, big-endian
u.bytes_le   # b'\x10\xb8\xa7k\xad\x9d\xd1\x11...'  Microsoft, little-endian
uuid.UUID(bytes=u.bytes_le)  # → 10b8a76b-ad9d-d111-80b4-00c04fd430c8

Python actually names this distinction out loud — bytes versus bytes_le. Most languages don't bother, which is sort of the whole problem in miniature.

What is a UUID? UUID vs. GUIDEye on Tech

Where this actually bites people

.NET's Guid.ToByteArray() hands you back the Microsoft layout, and if those bytes land on a system expecting RFC order, the value's changed the moment it crosses the wire. .NET 8 finally added a fix for this — ToByteArray(bigEndian: true), plus a matching constructor — and it's genuinely worth reaching for anywhere a GUID leaves the .NET world.

SQL Server's uniqueidentifier sorts in an order that matches neither the string form nor the raw bytes; it compares the last six bytes first, of all things. Two GUIDs that look practically adjacent as text can end up nowhere near each other in an index, which throws people who assumed ORDER BY id behaves lexicographically. It doesn't, here.

Cross-platform storage is the classic version of this whole mess — write a GUID out of a .NET service into a Postgres uuid column, read it back from a Python or Go service on the other end, and you've got the textbook setup for silent corruption. The database's just storing sixteen bytes. It has no idea which convention wrote them in.

The rule, if you only remember one thing from this section: pick one byte order at every boundary and be loud about which one you picked. Stick to the 36-character string everywhere and none of this can touch you at all.

Versions, and which one you actually want in 2026

The version lives in the first hex digit of the third group. 6ba7b810-9dad-11d1-… — that 1 right there marks it version 1.

  • v1 is a timestamp glued to a MAC address. Legacy at this point, and it leaks both the creation time and the machine that made it, which is its own small privacy problem nobody thinks about until someone points it out.
  • v3, an MD5 hash of a namespace plus a name. Useful when the same input needs to always produce the same output.
  • v4 is 122 random bits and nothing else. The one everybody means when they just say "UUID" without qualifying it.
  • v5 does the same deterministic trick as v3 but with SHA-1 instead, and it's the one you'd actually pick today if you need that property.
  • v6 is basically v1 with the timestamp bits reordered so it sorts properly — a bridge for people migrating away from v1 who still want sortability.
  • v7, a millisecond Unix timestamp plus random bits, and this is genuinely the best default for new database primary keys right now. More on why below.
  • v8 is a blank slate, whatever a vendor decides to put in there.

RFC 9562 replaced the older RFC 4122 back in May 2024 and brought v6, v7, and v8 along with it. A lot of "GUID vs UUID" pages still floating around online were written before that update even existed, so treat older advice about "the four UUID versions" with a little suspicion.

Why v7, specifically, matters for a database

Random v4 values land in random spots in a B-tree index, every single insert. That fragments pages and drags down write throughput once a table gets big — this is the actual, well-documented reason teams have historically steered away from UUID primary keys, not just vague unease about "they're long."

v7 fixes this by putting a 48-bit millisecond timestamp right up front, so newly generated values increase over time and land near each other in the index. You keep the thing that made UUIDs appealing in the first place, any node anywhere can mint one without asking permission, without paying the fragmentation tax that v4 charges you.

UUIDv7 layout
┌────────────────────────┬────┬────────┬──┬──────────────┐
│ unix_ts_ms (48 bits)   │ver │ rand_a │var│   rand_b     │
│ millisecond timestamp  │ 4  │  12    │ 2 │     62       │
└────────────────────────┴────┴────────┴──┴──────────────┘

Trade-off, obviously: v7 reveals roughly when a row was created, just by looking at it. For anything user-facing where that's a leak you'd rather not have — a public order ID, say — stick with v4 instead.

A quick, honest caveat: a browser-based generator that only outputs v4 can't hand you a v7 value, since v7 really wants to be minted right at insert time by your database or app runtime. Postgres 18 ships uuidv7() natively now, and most languages have a library for it if yours doesn't build it in yet.

Reading one, digit by digit

Two spots in the string actually mean something. Everything else is just payload.

6ba7b810-9dad-11d1-80b4-00c04fd430c8
                   ↑     ↑
                   │     └── variant: 8, 9, a, or b = standard RFC layout
                   └──────── version: 1 through 8

Version's the first character of the third group. Variant is the first character of the fourth group — 8, 9, a, or b there means you're looking at a normal RFC-standard UUID. A c or d instead points at the old Microsoft COM variant, and anything from 0 to 7 is the ancient Apollo NCS format nobody generates on purpose anymore.

Two special values worth knowing:

00000000-0000-0000-0000-000000000000   Nil UUID
ffffffff-ffff-ffff-ffff-ffffffffffff   Max UUID (new as of RFC 9562)

Both valid, neither random. And don't let the Nil one quietly become your codebase's stand-in for "no value" in a column that also holds real IDs — that's asking for a bug down the line when a real all-zero UUID (astronomically unlikely, sure, but not impossible) collides with your placeholder logic.

Can two of these actually collide?

A v4 UUID carries 122 random bits, since 4 get fixed for the version and 2 for the variant. That works out to roughly 5.3 × 10³⁶ possible values, a number large enough that writing it out doesn't really help anyone's intuition.

Using the birthday-paradox math, you'd need about 2.7 × 10¹⁸ UUIDs floating around before there's even a coin-flip chance of any two matching. Generating a billion a second, nonstop, that's something like 86 years before the odds tip past 50-50.

The caveat that actually matters more than any of that math: this all assumes a genuinely secure random source behind the generator. A v4 UUID minted from Math.random() or some badly seeded PRNG has nowhere near that real entropy, and a collision under those conditions isn't a freak statistical event, it's just a bug you wrote.

Where people actually get this wrong

Treating a UUID like a secret. It's an identifier, not a credential, and it shows up in URLs, server logs, referrer headers, analytics exports, all the places you'd rather a password never appear. A v4 UUID has enough entropy that nobody's guessing it outright, sure — but unguessable isn't the same thing as authorized, and you still need a permission check server-side. If something actually needs to be secret, generate an actual secret instead.

Assuming version 1 is somehow anonymous. It's not — it's got a timestamp and, in the original design, the generating machine's MAC address baked right in, and that detail has genuinely shown up in forensic investigations before.

Storing these as plain text when your database has a native type for them. A UUID crammed into a char(36) column costs 36 bytes plus overhead, versus 16 for an actual uuid type. Doesn't sound like much until you're at a hundred million rows, at which point it's gigabytes, and every index built on that column pays the same tax twice over.

Using v4 as a clustered primary key on a big, busy table — see the whole v7 section above. If switching isn't an option right now, at least consider a separate sequential key just for physical row ordering.

Expecting case to just not matter, anywhere. Hex is case-insensitive by spec, technically, but a varchar column comparing 6BA7B810… to 6ba7b810… under a case-sensitive collation will call those two different values. Normalize to lowercase the moment it's written and this never comes up again.

And using one anywhere a human has to actually type it out. Nobody reads a UUID aloud correctly, ever, not even once. Support tickets, order numbers, coupon codes — use a short code for the human-facing part and keep the UUID as the internal plumbing nobody has to say out loud over the phone.

Fast answers

Are GUID and UUID the same thing? As values and as strings, yes. As raw bytes, not always — see everything above.

Which one's the actual standard? UUID. RFC 9562 is current, having replaced RFC 4122.

Which version should I be generating? v7 for new database keys, v4 for pretty much everything else, v5 if you need the same input to always produce the same output.

How big is one? 128 bits. 16 bytes. 36 characters once it's written out as text.

Is it secret? No. Don't treat it like one.

Can two actually collide? Not in any practical sense, assuming a real random source behind the generation.

Braces or no braces? Braces are a Windows display convention, nothing more. Strip them before parsing, they're not part of the value.

Is v7 supported everywhere yet? Getting there, not universal. Postgres 18 has it built in, and most major languages have a library for it — worth checking your specific runtime before committing to it in production.

UUID or a plain auto-increment integer — which one? Integers are smaller and quicker when one single database is the only thing assigning IDs. Reach for UUIDs once multiple services, or offline clients, or anything outside that one database needs to mint an ID before the row even exists yet. Worth remembering too that sequential integers leak volume for free — anyone can glance at /orders/1042 and guess roughly how many orders you've ever had.

Why does my GUID show up with curly braces around it? That's Windows tooling formatting it that way, {6ba7b810-9dad-11d1-80b4-00c04fd430c8}. Purely cosmetic. Strip the braces before you try parsing it as a plain UUID.

Written by

Mateo Díaz

Builds, tests, and documents Textavia's tools, drawing on degrees in computer science and mathematics and a habit of explaining things plainly.

View author profile

Related tools

Related guides