Why UUIDs are better than auto-increment IDs - cover art

UUID and IDs 18 min read

Why UUIDs beat auto-increment IDs (and when they don't)

May 13, 2026 · 14 min read

Teams still argue about integer IDs versus UUIDs in 2026 because both sides are right under different constraints. This article is a decision guide, not a slogan. You will leave with criteria you can defend in a design review.

What auto-increment still does better

Where UUIDs clearly win

1. Public, guessable URLs

Sequential IDs leak business metrics (/users/10482 invites scraping). UUIDs remove trivial enumeration. Pair opaque IDs with authorization, not instead of it.

2. Distributed creation

Mobile apps, edge workers, and microservices can mint IDs without contacting a central sequence. That removes a single-writer bottleneck and failure domain.

3. Data merges and replication

Importing two databases both starting at id=1 is painful. UUID primary keys collide only in theory.

Costs people underestimate

-- PostgreSQL: UUID primary key (consider v7 generators in app layer)
CREATE TABLE orders (
  id UUID PRIMARY KEY,
  customer_id UUID NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

Hybrid patterns that work

Decision checklist

Choose UUIDs when two or more are true:

  1. IDs appear in client-visible URLs or mobile offline storage.
  2. Multiple writers generate rows without coordination.
  3. You merge datasets from independent environments.
  4. Enumeration risk has a real abuse story in your threat model.

Stick with integers when the database is single-region, internal-only, and join performance dominates.

Worked example: public API resource IDs

Imagine GET /api/invoices/10482. An attacker can scrape 10480-10490 in seconds. Switching to GET /api/invoices/7c9e6679-7425-40de-944b-e07fc1f90ae7 removes trivial enumeration. You still need authz checks - UUIDs are not magic - but you raise the cost of bulk harvesting.

Internally you might keep invoice_seq BIGSERIAL for reporting joins and expose only the UUID in JSON. ORMs make this easy with separate id and public_id columns. The extra column is cheaper than debugging a leaked integer sequence in a compliance review.

interface InvoiceDto {
  id: string;        // UUID exposed to clients
  // internalSeq?: never - not exposed
}

Sharding note: auto-increment per shard still collides when you merge shards unless you allocate non-overlapping ranges. UUIDs (especially v7) simplify merges at the cost of wider keys. Pick based on whether you expect shard merges in the product lifetime.

FAQ

Are UUIDs slower than integers?
Joins and index size can be slower with random v4. v7 and proper BINARY(16) storage narrow the gap.
Should API expose integer or UUID?
Expose opaque UUID (or ULID) if clients see IDs. Keep integers internal if not.
Do UUIDs break database replication?
No - they are values like any other PK. Random v4 can increase write amplification on replicas.
What about Snowflake IDs?
Snowflake-style IDs are 64-bit sortable integers - great for high-throughput logs. Use our Snowflake parser when debugging them.

Related: What is a UUID? · Bulk UUID generator