UUID vs GUID: Same Thing, Different Ecosystem
21 March, 2026 Backend
GUID and UUID are the same 128-bit identifier defined in RFC 4122. The distinction is ecosystem: Microsoft coined GUID (Globally Unique Identifier) for the COM/Windows world; the rest of the industry uses UUID (Universally Unique Identifier). Copy a GUID into a UUID field and it works - because they are structurally identical.
The nuances that do exist - case conventions, brace notation, SQL Server's sequential variant - are worth understanding because they affect interoperability, database performance, and a few non-obvious bugs.
The Core Format
Both UUID and GUID share the same 128-bit structure: five hyphen-separated groups of hex digits in an 8-4-4-4-12 layout:
550e8400-e29b-41d4-a716-446655440000
xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx
↑ ↑
version=4 variant bits (RFC 4122)
The version nibble is 4, variant bits are 10xx (hex 8-b), and the remaining 122 bits are random. This structure is identical whether you call it a UUID or a GUID. Try the UUID v4 generator to see the format in action.
The Differences That Actually Exist
Case Convention
UUIDs are lowercase everywhere outside the Microsoft ecosystem:
# UUID - standard, lowercase
550e8400-e29b-41d4-a716-446655440000
# GUID - Windows/COM/SQL Server output, uppercase
550E8400-E29B-41D4-A716-446655440000
This is cosmetic at the data level - hex is case-insensitive - but it causes real problems with string comparisons in case-sensitive databases (MySQL with utf8_bin collation, for example) and in application code that does naive string equality checks instead of normalising first.
Brace Notation
Windows registry, COM interfaces, and some .NET serialization formats wrap the GUID in curly braces:
{550E8400-E29B-41D4-A716-446655440000}
The braces are not part of the identifier - they are a display convention. Guid.ToString("B") in C# produces the braced form; Guid.ToString("D") produces bare digits. When exchanging IDs between a .NET service and anything else, strip the braces and normalise case before comparison.
Variant Bits
Historical COM GUIDs used a different variant encoding (bits 62-63 = 110, hex c/d) from the RFC 4122 variant (10, hex 8-b). Modern Guid.NewGuid() generates RFC 4122-compliant v4 UUIDs, so this difference only surfaces with legacy COM identifiers embedded in old registry entries or Windows system files.
SQL Server Specifics
The uniqueidentifier Type
SQL Server stores GUIDs as uniqueidentifier (16 bytes, uppercase display). NEWID() generates a random v4-equivalent GUID - the analog of PostgreSQL's gen_random_uuid():
SELECT NEWID();
-- 6F9619FF-8B86-D011-B42D-00CF4FC964FF
Index Fragmentation Problem
Random GUIDs as clustered primary keys cause B-tree fragmentation. Every NEWID() value lands at a random position in the tree, forcing random page reads and triggering page splits (a page fills up, gets split in two, both pages end up ~50% full). Over millions of inserts the index is riddled with half-empty pages; range scans become random I/O.
The effect is measurable at ~5-10 million rows and severe beyond 50 million on write-heavy tables.
NEWSEQUENTIALID(): SQL Server's Solution
NEWSEQUENTIALID() generates monotonically increasing GUIDs within a server restart boundary:
CREATE TABLE orders (
id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID(),
created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
CONSTRAINT PK_orders PRIMARY KEY CLUSTERED (id)
);
Sequential GUIDs insert at the rightmost leaf of the B-tree, eliminating page splits. The trade-off: values reset to a new (but still increasing) sequence on server restart, and the sequential pattern reveals insertion order in public-facing IDs.
NEWSEQUENTIALID() cannot be called outside a DEFAULT constraint - it cannot be invoked in application code. To retrieve the generated GUID after insert, use the OUTPUT clause:
INSERT INTO orders (payload) OUTPUT INSERTED.id VALUES ('...');
UUID v7: The Cross-Platform Solution
SQL Server's NEWSEQUENTIALID() solves fragmentation but requires server-side generation. If you need the ID in application code before the insert - to include it in a message queue payload, use it in multiple tables, or avoid a round trip - UUID v7 is the right choice:
018e2b3d-a1c2-7000-b0e5-4a9f1c8d7e2a
└──────────────┘
48-bit Unix ms timestamp
UUID v7 is monotonically increasing (millisecond precision), RFC 9562 standardised, and generated in application code. It solves the same fragmentation problem as NEWSEQUENTIALID() while working on any database and any platform. Compare the output with the UUID v7 generator to see the embedded timestamp.
.NET: Working with GUIDs
Guid id = Guid.NewGuid(); // RFC 4122 v4
id.ToString("D"); // 550e8400-e29b-41d4-a716-446655440000 (no braces)
id.ToString("B"); // {550E8400-E29B-41D4-A716-446655440000} (braces)
id.ToString("N"); // 550e8400e29b41d4a716446655440000 (no hyphens)
Guid.Parse("{550E8400-E29B-41D4-A716-446655440000}"); // accepts any case + braces
Guid.TryParse(input, out Guid result);
Guid.Empty; // 00000000-0000-0000-0000-000000000000
// EF Core + SQL Server: DB generates the sequential GUID
public class Order
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
For application-side sequential GUIDs, use the UuidNext NuGet package - it generates v7-compatible time-ordered values without a database round trip.
PHP: ramsey/uuid and Symfony
<?php
// ramsey/uuid
use Ramsey\Uuid\Uuid;
$id = Uuid::uuid4()->toString(); // random
$id = Uuid::uuid7()->toString(); // time-ordered
// symfony/uid (ships with Symfony)
use Symfony\Component\Uid\Uuid;
$v4 = Uuid::v4()->toRfc4122();
$v7 = Uuid::v7()->toRfc4122();
$binary = Uuid::v7()->toBinary(); // 16-byte string for BINARY(16) column
Uuid::v7() is the right default for new Symfony projects on MySQL or any database with a clustered primary key index.
Common Mistakes
Storing as VARCHAR(36) Instead of a Native Type
-- Wrong: wastes space, slower comparisons, prevents native functions
id VARCHAR(36) NOT NULL
-- Correct for SQL Server
id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID()
-- Correct for PostgreSQL
id UUID NOT NULL DEFAULT gen_random_uuid()
-- Correct for MySQL (no native UUID type before 8.0)
id BINARY(16) NOT NULL
At 100 million rows, VARCHAR(36) vs BINARY(16) for the primary key index alone is a ~2 GB difference. String comparison for GUIDs requires a full 36-byte compare; binary comparison is fixed-width 16 bytes and significantly faster at scale.
Generating IDs in the Database When You Need Them Earlier
If your application needs the primary key before the insert - for a message queue payload, a distributed trace, or a second table in the same transaction - database-side generation forces an insert-then-read. Application-side UUID v7 generation eliminates the round trip.
Decision Table
| Use case | Recommendation |
|---|---|
| General purpose, no specific constraints | UUID v4 |
| Database primary key, write-heavy table | UUID v7 |
| SQL Server, server-side generation | NEWSEQUENTIALID() |
| .NET application-side generation | Guid.NewGuid() (v4) or UuidNext (v7) |
| MongoDB document IDs | ObjectID (12-byte, built-in) |
| Deterministic ID from known input | UUID v5 |
| Legacy COM / Windows registry | GUID (whatever format the system expects) |
The Short Version
GUID and UUID are the same identifier. The practical differences are case convention, brace notation in Windows contexts, and SQL Server's NEWSEQUENTIALID() extension. For new systems, use UUID v7 for primary keys — it solves fragmentation, works with application-side generation, and is IETF standardised.