Axonix Tools
UUIDs: The Complete Developer's Guide to Unique Identifiers
Back to Insights
UUIDGUIDDeveloper Tools

UUIDs: The Complete Developer's Guide to Unique Identifiers

12 min read

Master UUIDs (Universally Unique Identifiers) with this comprehensive guide. Learn all 8 versions, implementation strategies, performance optimization, and when to use alternatives like ULIDs and NanoIDs. Includes practical code examples for JavaScript, Python, Go, and SQL.

The $47,000 Email That Broke Everything

Okay, real talk. I need to tell you about the worst week of my dev career.

November 2020. Peak pandemic chaos. My startup just got acquired (yay!) which meant merging databases with the parent company (oh no). Both systems used auto-increment integers. You see where this is going, right?

User #1 in our database definitely isn't User #1 in theirs. So the "brilliant" solution? Just add a million to all our IDs. Problem solved! We had like 50,000 users. Plenty of buffer. What could go wrong?

Everything. Everything could go wrong.

Someone—I won't name names but it rhymes with "me"—forgot about foreign keys in three tables. Orders started linking to random customers. Support tickets attached to accounts that never filed them. And then came the email.

One very confused user got a message thanking them for a $47,000 enterprise purchase they definitely didn't make. Cue three days of panic, database archaeology, and apologizing to very confused people.

If we'd used UUIDs from day one? None of this happens. The IDs would've been unique across both databases automatically. No collision possible.

That's why I'm borderline obsessive about UUIDs now. Let me save you from my pain.

WTF Is a UUID Anyway?

UUID stands for Universally Unique Identifier. Sounds fancy, right? It's really just a 128-bit number dressed up in a specific format:

550e8400-e29b-41d4-a716-446655440000

Breaking it down because I know you're curious:

  • 32 hexadecimal characters (0-9, a-f)
  • 5 groups separated by hyphens
  • 128 bits total (16 bytes if you store it as binary)
  • That weird format: 8-4-4-4-12 characters

Microsoft calls them GUIDs (Globally Unique Identifiers). Same thing, different name. I don't know why they needed their own acronym, but here we are.

The Math That'll Blow Your Mind

So why do UUIDs work? Combinatorics. Bear with me here.

For UUID version 4 (the random one everyone uses):

  • 122 bits of actual randomness (6 bits reserved for metadata)
  • Total possible combinations: 2^122 ≈ 5.3 × 10^36

Let me put that in perspective for you:

  • Generate 1 billion UUIDs per second
  • Keep that up for 85 years straight
  • You'd have a 50% chance of ONE collision

That's... that's insane. You'd need to generate roughly 10 trillion UUIDs before you hit lottery-level odds of a duplicate. For all practical purposes, they're unique. Full stop.

I spent way too long calculating this once instead of doing actual work. No regrets.

All 8 UUID Versions (Yeah, There Are 8)

I used to think there were just 2 versions. I was so wrong. There are actually 8, and they all do different things. Let me break this down because I wish someone had explained this to me years ago.

Version 1: Timestamp + MAC Address

The structure: Timestamp (60 bits) + Clock sequence (14 bits) + MAC address (48 bits)

Why you'd use it:

  • Has timestamp info built right in
  • Mostly increases monotonically (good for sorting)
  • Guaranteed unique across space and time... theoretically

Why you probably shouldn't:

  • MASSIVE privacy concern — it leaks your MAC address and when the ID was generated
  • Hackers can guess these if they know your setup
  • Clock sequence issues on some systems

I learned about the privacy thing the hard way during a security audit. Not fun.

// Node.js example
const { v1 } = require("uuid");
const uuid = v1(); // '6ba7b810-9dad-11d1-80b4-00c04fd430c8'

Version 2: DCE Security

Status: Basically nobody uses this.

It's like v1 but includes a local domain identifier for POSIX UID/GID. I've literally never seen this in production. Moving on.

Version 3: Name-Based (MD5)

The deal: Creates a UUID by hashing a namespace + name with MD5.

The catch: It's deterministic. Same inputs = same UUID every time.

Why this matters:

  • Good for content-addressable storage
  • Can regenerate the same UUID from the same name later

Why it's problematic:

  • MD5 is cryptographically broken (collisions are possible)
  • Don't use this for new projects. Just don't.
const { v3 } = require("uuid");
const MY_NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
const uuid = v3("hello.example.com", MY_NAMESPACE);
// Always: 'c934e18f-9411-32d5-9b8d-168649cf9102'

Version 4: Random (The One You Actually Use)

This is what 95% of developers mean when they say "UUID." It's random. That's it.

Pros:

  • Dead simple
  • No privacy leaks
  • No coordination needed between systems

Cons:

  • No ordering (bad for database indexes)
  • Takes more storage than integers (16 bytes vs 4-8 bytes)

Honestly? Just use v4 unless you have a specific reason not to. It's fine.

const { v4 } = require("uuid");
const uuid = v4(); // 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'

Version 5: Name-Based (SHA-1)

Like v3 but uses SHA-1 instead of MD5. Better hash function, still deterministic.

I guess if you absolutely need deterministic UUIDs and MD5 makes you nervous, use this? Though honestly, just use v4 and be done with it.

Version 6: Reordered Timestamp

Okay, this one's actually cool. It's like v1 but reorders the timestamp bits so it sorts chronologically.

Why this matters for databases: Chronological sorting = happier indexes = better performance.

The catch: Still has that MAC address privacy issue from v1. Ugh.

Version 7: Timestamp + Random (The New Hotness)

Now THIS is what I'm talking about. Released in... well, recently. Combines the best of both worlds:

  • Has a Unix timestamp (sortable!)
  • Adds randomness (secure!)
  • No MAC address leak (private!)
  • Chronologically sortable (database-friendly!)

If you're starting a new project in 2025 or later, use v7. Seriously. It's what v4 should've been.

// Using uuidv7 library
import { uuidv7 } from "uuidv7";
const uuid = uuidv7(); // Contains embedded timestamp

Version 8: Custom

Reserved for experimental stuff. I've never seen it used. You probably won't either.

The Database Performance Reality Check

Here's something that took me embarrassingly long to figure out: UUID v4s are RANDOM. This matters way more than you'd think.

The Indexing Problem Nobody Talks About

When you insert a new row with a random UUID v4, it goes... somewhere random in your B-tree index. For high-write systems, this causes:

  • Page splits (expensive)
  • Fragmentation (slower reads)
  • Cache thrashing (goodbye performance)
  • Write amplification (more disk I/O)

I learned this when our write-heavy API started grinding to a halt. Switched to v7 and watched our database CPU drop by 40%. Live and learn.

Storage Comparison (The Numbers)

| Type | Size | Can You Sort It? | Notes | | ---------------- | -------- | ---------------- | --------------------------------- | | Integer (32-bit) | 4 bytes | Yes | Too small for distributed systems | | Integer (64-bit) | 8 bytes | Yes | Better, but still sequential | | UUID (string) | 36 bytes | No | Human readable but huge | | UUID (binary) | 16 bytes | Kinda | The sweet spot | | ULID | 26 bytes | Yes | Sortable alternative |

Database-Specific Optimization

PostgreSQL (my personal favorite):

-- Native UUID type stores as 16 bytes
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) NOT NULL
);

-- Extensions for v7 if you want the good stuff

MySQL (because sometimes you have to):

-- BINARY(16) is way more efficient than CHAR(36)
CREATE TABLE users (
    id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID())),
    email VARCHAR(255) NOT NULL
);

MongoDB:

// ObjectId is already basically a v1 UUID
// But you can use real UUIDs if you want
db.users.insertOne({
  _id: UUID("550e8400-e29b-41d4-a716-446655440000"),
  email: "user@example.com",
});

When UUIDs Aren't the Answer (And What To Use Instead)

Look, I love UUIDs. But they're not always the right tool. Here's my honest take on alternatives:

ULID (Universally Unique Lexicographically Sortable Identifier)

What it is: 26-character string with timestamp + randomness

Why I like it:

  • Sortable by time (like v7 but shorter)
  • URL-safe (no weird characters)
  • Monotonic (great for databases)

The downside: Not a formal standard. But honestly, who cares? It works.

import { ulid } from "ulid";
const id = ulid(); // '01ARZ3NDEKTSV4RRFFQ69G5FAV'

NanoID

What it is: Shorter random strings (21 chars by default)

Why it's cool:

  • Smaller than UUID
  • Faster generation
  • Customizable alphabet

The catch: Not time-sortable at all. Just pure randomness.

import { nanoid } from "nanoid";
const id = nanoid(); // 'V1StGXR8_Z5jdHi6B-myT'

Snowflake IDs (Twitter/X Style)

What it is: 64-bit integers with embedded timestamp

Structure: Timestamp (41 bits) + Machine ID (10 bits) + Sequence (12 bits)

Why it's awesome:

  • Sortable by time
  • Compact (8 bytes vs 16 for UUID)
  • Scalable across distributed systems

Why it's annoying:

  • Requires coordination (machine IDs, clock sync)
  • More infrastructure to manage
const Snowflake = require("snowflake-id");
const snowflake = new Snowflake({ mid: 1 });
const id = snowflake.generate(); // 1275383897512263680n

My Personal Rules for UUID Usage

After years of screwing this up, here's what I actually do:

1. Storage Format Matters

// ❌ Bad: String (36 bytes, slow comparisons)
const uuidString = "550e8400-e29b-41d4-a716-446655440000";

// ✅ Good: Binary (16 bytes, fast comparisons)
const uuidBinary = Buffer.from(uuidString.replace(/-/g, ""), "hex");

2. Always Validate (Never Trust Input)

function isValidUUID(str) {
  const uuidRegex =
    /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  return uuidRegex.test(str);
}

// I learned this after a user somehow submitted "not-a-uuid" and crashed production

3. Case Sensitivity Is A Trap

-- Always store lowercase
INSERT INTO users (id) VALUES (LOWER('550E8400-E29B-41D4-A716-446655440000'));

-- Query lowercase too
SELECT * FROM users WHERE id = LOWER('550E8400-E29B-41D4-A716-446655440000');

Learned this the hard way when string comparisons failed and I spent an afternoon debugging why WHERE id = ? returned nothing. Fun times.

4. URL Safety

Full UUIDs in URLs look terrible:

❌ https://api.example.com/users/550e8400-e29b-41d4-a716-446655440000

Use Base64 or Base58 instead:

✅ https://api.example.com/users/VQ6EAOKbQdSnFkRmVUQAAA

The Decision Matrix I Wish I Had Years Ago

| Scenario | What I Use | Why | | ----------------------------------- | ---------------------- | ----------------------------------- | | General purpose apps | UUID v4 | It just works, universal support | | High-write databases | UUID v7 | Time-ordered, index-friendly | | Public URLs | NanoID or ULID | Shorter, cleaner | | Microservices / distributed systems | Snowflake | Sortable, scalable | | Content-addressable storage | UUID v5 | Deterministic from content | | Legacy system integration | UUID v1 | Timestamps embedded (unfortunately) |

Common Pitfalls I've Hit (So You Don't Have To)

Pitfall 1: Using UUIDs For Tiny Tables

Don't use UUIDs for lookup tables with <1000 rows. Just use integers. You're not gonna merge your country codes table with anyone. I did this once and my DBA laughed at me for weeks.

Pitfall 2: UUIDs In URLs Without Optimization

See above. Long URLs look ugly and hit browser limits eventually. Plus they make logs unreadable.

Pitfall 3: Case Sensitivity Mixups

I mentioned this already but it bears repeating. Different systems treat UUID case differently. Normalize to lowercase everywhere. Trust me on this one.

Pitfall 4: Mixing UUID Versions In The Same Column

Don't store v1 and v4 in the same column if you rely on ordering. They're incompatible and will sort weirdly. Voice of experience here.

Pitfall 5: Using UUID v4 For Sorting

UUID v4 is random. Sorting by it means nothing. It's like shuffling a deck of cards and then trying to sort them by... nothing. Use v7 or add a separate timestamp column.

Real Code Examples (That Actually Work)

Node.js / Express REST API

const express = require("express");
const { v4: uuidv4 } = require("uuid");

const app = express();
app.use(express.json());

app.post("/users", (req, res) => {
  const user = {
    id: uuidv4(),
    email: req.body.email,
    createdAt: new Date().toISOString(),
  };

  // Save to your database here

  res.status(201).json(user);
});

app.get("/users/:id", (req, res) => {
  const { id } = req.params;

  // Validate the UUID format because users type weird stuff
  const uuidRegex =
    /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  if (!uuidRegex.test(id)) {
    return res.status(400).json({ error: "Invalid UUID format" });
  }

  // Fetch from database...
});

Python / FastAPI (Because Why Not)

from fastapi import FastAPI
from pydantic import BaseModel, UUID4
from uuid import uuid4

app = FastAPI()

class User(BaseModel):
    id: UUID4
    email: str

@app.post("/users", response_model=User)
async def create_user(email: str):
    user = User(id=uuid4(), email=email)
    # Save to database...
    return user

Go (For When You Need Speed)

package main

import (
    "github.com/google/uuid"
    "github.com/gin-gonic/gin"
)

type User struct {
    ID    uuid.UUID `json:"id"`
    Email string    `json:"email"`
}

func createUser(c *gin.Context) {
    var input struct {
        Email string `json:"email" binding:"required"`
    }

    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    user := User{
        ID:    uuid.New(),
        Email: input.Email,
    }

    // Save to database...

    c.JSON(201, user)
}

The Bottom Line (Finally)

UUIDs solve the distributed ID generation problem elegantly. For most stuff, UUID v4 is perfectly fine—stop overthinking it.

But as you scale (or if you're starting fresh in 2026), consider:

  1. UUID v7 for new projects (time-ordered = happy databases)
  2. ULID or NanoID for URL-visible IDs (shorter is better)
  3. Snowflake when you're building the next Twitter (massive scale)
  4. UUID v5 when you need deterministic IDs from names

The key is picking the right tool for your specific situation, not just defaulting to UUID v4 because it's familiar.

Though honestly? If you're reading this and thinking "crap, I should switch from v4 to v7," don't panic. v4 works fine. v7 is just... better. Upgrade when you have time, not at 2 AM because something's on fire.

FAQ (Questions People Actually Ask Me)

Q: Can I generate UUIDs in the browser safely? A: Yep! Use crypto.randomUUID() for v4, or grab a library for v7. It's totally fine.

Q: Should I expose UUIDs to users? A: Generally yeah, but consider using shorter formats (Base58) for URLs. Nobody wants to type a full UUID.

Q: How do I migrate from auto-increment to UUID? A: Carefully. Add a new UUID column, backfill with generated values, migrate foreign keys one table at a time, then switch over. Test everything twice. Maybe three times.

Q: Are UUIDs truly unique? A: Statistically? Yeah. Practically? For all real-world purposes. The collision probability is so low you'd win the lottery multiple times first.

Q: Why not just use auto-increment everywhere? A: They don't work across distributed systems, merge terribly (as I learned the hard way), and leak info (sequential IDs reveal your growth rate to competitors).

Q: UUID v7 sounds great but my library doesn't support it. A: Most don't yet. You can either use a dedicated v7 library, wait for mainstream support, or just stick with v4. The world's not gonna end if you use v4.


Ready to generate some UUIDs? Try our free UUID Generator — it supports all versions and bulk generation because sometimes you need 1000 UUIDs and don't want to write a script.

Now go forth and generate unique IDs. And please, for the love of all that is holy, don't add a million to auto-increment IDs when merging databases. Learn from my pain.

Written by Axonix Team

Axonix Team - Technical Writer @ Axonix

Share this article

Discover More

View all articles

Ready to boost your productivity?

Axonix provides 20+ free developer tools to help you code faster and more securely.

Explore Our Tools