Question

Generate UUID Version 4 in Go: Random Bytes, Variant, and Version Bits

gouuid

Question

How can I generate a UUID in Go?

I have code similar to the following:

u := make([]byte, 16)
_, err := rand.Read(u)
if err != nil {
    return
}

u[8] = (u[8] | 0x80) & 0xBF
u[6] = (u[6] | 0x40) & 0x4F

return hex.EncodeToString(u)

The result is a 32-character hexadecimal string, but it does not look like a typical UUID with hyphens. Is it a valid UUID? Why does the code modify u[8] and u[6], and is there a better way to generate UUIDs in Go?

Short Answer

A UUID version 4 is 16 random bytes with a few bits reserved to identify its format. In Go, crypto/rand can generate the random bytes, and bitwise operations set the required version and variant bits. A 32-character hexadecimal result contains the correct 16 bytes; hyphens are only a display format. In production, a maintained UUID library is often the clearest choice.

Concept

A UUID (Universally Unique Identifier) is a 128-bit value, which is exactly 16 bytes. UUIDs are commonly displayed as hexadecimal text, such as:

550e8400-e29b-41d4-a716-446655440000

A UUID version 4 (UUIDv4) is based mostly on cryptographically secure random data. It starts with 16 random bytes, but six bits must be set to fixed values:

  • The version field identifies this as version 4.
  • The variant field identifies the standard UUID layout used by most software.

That leaves 122 random bits, making accidental collisions extraordinarily unlikely when a secure random source is used.

In Go, use crypto/rand, not math/rand, for UUIDv4 values. crypto/rand obtains unpredictable random bytes from the operating system, which is appropriate for identifiers that may be exposed publicly or used in security-sensitive contexts.

A UUID can be represented in more than one textual form:

550e8400e29b41d4a716446655440000      // 32 hexadecimal characters
550e8400-e29b-41d4-a716-446655440000  // conventional hyphenated form

Both contain the same 16 bytes. The hyphens do not add data; they only improve readability.

Mental Model

Think of a UUIDv4 as a 16-slot card filled with random ink.

  • First, Go fills every slot randomly.
  • Then the UUID rules require changing two small labels on the card:
    • one label says, “This is version 4,”
    • the other says, “This uses the standard UUID variant.”

The code changes only those required label bits. Almost all of the remaining bits stay random.

The hyphens in a UUID string are like spaces in a phone number: they make it easier for humans to read, but they do not change the underlying value.

Take Quiz

Syntax and Examples

Generate 16 secure random bytes, then set the UUIDv4 version and variant fields:

package main

import (
	"crypto/rand"
	"fmt"
)

func newUUIDv4() (string, error) {
	u := make([]byte, 16)
	if _, err := rand.Read(u); err != nil {
		return "", err
	}

	// Set version 4: 0100xxxx
	u[6] = (u[6] & 0x0f) | 0x40

	// Set RFC 4122 / RFC 9562 variant: 10xxxxxx
	u[8] = (u[8] & 0x3f) | 0x80

	return fmt.Sprintf(
		"%08x-%04x-%04x-%04x-%012x",
		u[0:4], u[4:6], u[6:8], u[8:10], u[10:16],
	), nil
}

func main() {
	id, err := newUUIDv4()
	if err != nil {
		panic(err)
	}

	fmt.Println(id)
}

Example output:

c4d21d28-dafd-4e43-a81f-2e5bf035f1db

The important bit operations are commonly written in this order:

u[6] = (u[6] & 0x0f) | 0x40
u[8] = (u[8] & 0x3f) | 0x80
  • & clears the bits that must be controlled.
  • | sets the required bits.

The question's expressions are equivalent:

u[8] = (u[8] | 0x80) & 0xBF
u[6] = (u[6] | 0x40) & 0x4F

For u[8], the code ensures the two highest bits are 10. For u[6], it ensures the four highest bits are 0100. The alternative form using & first is more widely recognized and easier to verify visually.

Step by Step Execution

Consider this simplified example, where the random bytes at the required positions are:

u[6] = 0b10110111
u[8] = 0b01101101

Set the version on byte 6:

u[6] = (u[6] & 0x0f) | 0x40

Step by step:

u[6]             10110111
u[6] & 0x0f      00000111  // clear the highest four bits
0x40              01000000  // version 4 marker
result            01000111

The first four bits are now 0100, so this is version 4. The lower four bits remain random.

Set the variant on byte 8:

u[8] = (u[8] & 0x3f) | 0x80

Step by step:

u[8]             01101101
u[8] & 0x3f      00101101  // clear the highest two bits
0x80              10000000  // standard UUID variant marker
result            10101101

The first two bits are now 10, while the lower six bits remain random.

Finally, formatting the 16 bytes as hexadecimal produces 32 hex digits because every byte requires two hexadecimal characters:

16 bytes × 2 hex characters per byte = 32 hex characters

Adding hyphens creates the usual 8-4-4-4-12 display grouping without changing the UUID data.

Real World Use Cases

UUIDv4 values are useful when an application needs identifiers without asking a central database for the next number.

Common uses include:

  • API resource IDs: Create an order ID or upload ID before saving the resource.
  • Database primary keys: Use a UUID column when records may be created by multiple services or offline clients.
  • File names and object storage keys: Avoid collisions for uploaded files, exports, or generated reports.
  • Tracing and correlation: Attach an ID to a request so logs from several services can be connected.
  • Invitation and public links: Generate difficult-to-guess references, while still enforcing authorization separately.
  • Distributed systems: Allow independently running workers to create IDs without coordinating a shared counter.

A UUID is an identifier, not permission. Do not treat possession of a UUID alone as sufficient authorization for sensitive data.

Take Quiz

Real Codebase Usage

In real Go projects, developers usually avoid manually formatting and validating UUIDs throughout the codebase. A common pattern is to use a maintained package, such as github.com/google/uuid.

package main

import (
	"fmt"

	"github.com/google/uuid"
)

func main() {
	id := uuid.NewString()
	fmt.Println(id)
}

Install it with:

go get github.com/google/uuid

The package handles secure random generation and standard formatting.

A typical service-layer pattern creates an ID at the boundary where an entity is created:

package orders

import "github.com/google/uuid"

type Order struct {
	ID       uuid.UUID
	Customer string
}

func NewOrder(customer string) Order {
	return Order{
		ID:       uuid.New(),
		Customer: customer,
	}
}

For incoming API data, parse and validate IDs before using them:

id, err := uuid.Parse(idFromURL)
if err != nil {
	return fmt.Errorf("invalid order ID: %w", err)
}

This keeps UUID generation, storage, parsing, and validation consistent. If you generate UUIDs manually, keep the generator in one well-tested function rather than repeating bit manipulation in multiple places.

Common Mistakes

Using math/rand

This is inappropriate for UUIDv4 generation when unpredictability matters:

// Do not use this for UUIDv4 generation.
import "math/rand"

value := rand.Int63()

math/rand is a deterministic pseudorandom generator. Use crypto/rand instead:

import "crypto/rand"

Forgetting the version and variant bits

This creates 16 random bytes, but not necessarily a conforming UUIDv4:

u := make([]byte, 16)
_, err := rand.Read(u)
if err != nil {
	return "", err
}

return hex.EncodeToString(u), nil

Fix it by setting both required fields:

u[6] = (u[6] & 0x0f) | 0x40
u[8] = (u[8] & 0x3f) | 0x80

Reversing the bitwise operation

This does not preserve the intended random bits:

// Broken: it keeps the wrong portion of the byte.
u[6] = (u[6] & 0x40) | 0x0f

Use a mask that clears only the controlled bits, then set their required pattern.

Assuming hyphens determine validity

A 32-character hexadecimal form can represent the same UUID as a hyphenated form. However, some parsers or APIs require a particular string format. Match the format required by the receiving system.

Ignoring errors from rand.Read

Randomness can fail in rare system-level situations. Propagate the error rather than returning a partially initialized identifier.

Treating UUIDs as secrets or authorization

UUIDv4 values are hard to guess, but authorization must still be checked. For example, an API should verify that the authenticated user is allowed to access the resource identified by the UUID.

Comparisons

ChoiceBest forImportant detail
Manual UUIDv4 with crypto/randLearning, small dependency-free utilitiesYou must correctly set bits and format output.
github.com/google/uuidMost Go applicationsClear API for generating, parsing, and formatting UUIDs.
math/randSimulations and non-security randomnessDo not use it to generate UUIDv4 identifiers that should be unpredictable.
Auto-incrementing integerOne centralized databaseCompact and ordered, but requires coordination and is easy to enumerate.
UUIDv4Distributed identifier generationRandom, non-sequential, and does not reveal record count.
UUIDv7Time-ordered UUID use casesOften preferable when roughly time-sortable IDs help database indexing or ordering.

A UUIDv4 is 128 bits of which 122 bits are random after setting the required metadata bits. It is ideal when random, decentralized ID generation matters more than chronological ordering.

Take Quiz

Cheat Sheet

import "crypto/rand"
u := make([]byte, 16)
if _, err := rand.Read(u); err != nil {
	return "", err
}

// UUID version 4: byte 6 begins with 0100.
u[6] = (u[6] & 0x0f) | 0x40

// Standard UUID variant: byte 8 begins with 10.
u[8] = (u[8] & 0x3f) | 0x80
ItemValue
UUID size16 bytes / 128 bits
Compact hexadecimal form32 characters
Conventional string form8-4-4-4-12 hexadecimal groups
Secure Go randomnesscrypto/rand.Read
UUIDv4 version bitsu[6] high nibble is 0100
Standard variant bitsu[8] high two bits are 10

Equivalent masks from the original code:

u[6] = (u[6] | 0x40) & 0x4f
u[8] = (u[8] | 0x80) & 0xbf

Prefer the conventional form because its intention is clearer:

u[6] = (u[6] & 0x0f) | 0x40
u[8] = (u[8] & 0x3f) | 0x80

FAQ

Does Go's standard library provide a UUID generator?

Go's standard library provides crypto/rand for secure random bytes, but it does not provide a dedicated UUID type and UUIDv4 generator. You can implement UUIDv4 with crypto/rand or use a package such as github.com/google/uuid.

Is a 32-character hexadecimal UUID valid without hyphens?

Yes. Sixteen bytes encode as 32 hexadecimal characters. Hyphens are a conventional presentation format, although a particular API or parser may require them.

Why are bytes 6 and 8 modified for UUIDv4?

They contain the standardized metadata fields. Byte 6 is changed to indicate version 4, and byte 8 is changed to indicate the standard UUID variant.

What does (u[6] & 0x0f) | 0x40 mean?

u[6] & 0x0f clears the upper four bits. | 0x40 then sets those upper bits to 0100, the UUIDv4 version marker.

Is crypto/rand.Read safe for generating UUIDs?

Yes. It reads cryptographically secure random data from the operating system and is the correct Go random source for UUIDv4 generation.

Can UUIDv4 values collide?

Theoretically yes, because they are random. In practice, UUIDv4 has 122 random bits, so collisions are extraordinarily unlikely when generated correctly.

Should I use UUIDv4 or UUIDv7?

Use UUIDv4 for broadly compatible random identifiers. Consider UUIDv7 when time-ordered identifiers are useful, such as for improved insertion locality in some database workloads.

Are UUIDs secure access tokens?

No. A UUID can be part of a URL or resource identifier, but sensitive operations must still authenticate and authorize the caller.

Take Quiz

Mini Project

Description

Build a small command-line Go program that generates a requested number of UUIDv4 values. The project demonstrates secure random byte generation, the required UUID version and variant bit masks, and conventional UUID formatting.

Goal

Create a program that prints one or more valid, hyphenated UUIDv4 strings.

Requirements

Use crypto/rand to fill a 16-byte slice with random data. Set the UUIDv4 version bits and standard variant bits. Format each value as a hyphenated 8-4-4-4-12 UUID string. Accept an optional command-line argument for how many UUIDs to generate. Return an error if random data cannot be read.

Take Quiz

Keep learning

Related questions

View more