How Go’s Snowflake Library Generates Distributed Unique IDs

This article explains the Snowflake distributed ID algorithm, breaks down the Go implementation in the bwmarrin/snowflake package, and details how initialization, node validation, timestamp handling, and bit‑wise composition work together to produce unique, time‑ordered 64‑bit identifiers in high‑concurrency environments.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
How Go’s Snowflake Library Generates Distributed Unique IDs

Overview

In high‑concurrency distributed systems a unique, time‑ordered identifier is required. The Snowflake algorithm generates 64‑bit IDs composed of a timestamp, datacenter ID, machine ID and a per‑millisecond sequence. The Go package bwmarrin/snowflake implements this algorithm with a Node type that safely produces IDs under heavy load.

Snowflake diagram
Snowflake diagram

Snowflake ID format

Sign bit (1 bit): always 0.

Timestamp (41 bits): milliseconds since a custom epoch.

Datacenter ID (5 bits).

Machine ID (5 bits).

Sequence (12 bits): counter that resets each millisecond.

Node initialization (NewNode)

func NewNode(node int64) (*Node, error) {
    mu.Lock()
    nodeMax = -1 ^ (-1 << NodeBits)
    nodeMask = nodeMax << StepBits
    stepMask = -1 ^ (-1 << StepBits)
    timeShift = NodeBits + StepBits
    nodeShift = StepBits
    mu.Unlock()

    n := Node{}
    n.node = node
    n.nodeMax = -1 ^ (-1 << NodeBits)
    n.nodeMask = n.nodeMax << StepBits
    n.stepMask = -1 ^ (-1 << StepBits)
    n.timeShift = NodeBits + StepBits
    n.nodeShift = StepBits

    if n.node < 0 || n.node > n.nodeMax {
        return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10))
    }

    curTime := time.Now()
    // Use a monotonic clock when possible to avoid time‑adjustment glitches
    n.epoch = curTime.Add(time.Unix(Epoch/1000, (Epoch%1000)*1000000).Sub(curTime))

    return &n, nil
}

The function locks a global mutex, calculates the maximum node ID ( nodeMax), the node mask, the step mask and the shift amounts ( timeShift, nodeShift). It then creates a Node instance, validates the supplied node value, and sets a custom epoch using time.Now() combined with time.Unix to obtain a monotonic base time. The initialized Node pointer is returned.

Node struct definition

type Node struct {
    mu        sync.Mutex
    epoch     time.Time
    time      int64 // last timestamp in milliseconds
    node      int64 // node identifier
    step      int64 // sequence within the same millisecond
    nodeMax   int64
    nodeMask  int64
    stepMask  int64
    timeShift uint8
    nodeShift uint8
}

The struct holds all state required for ID generation, including a mutex for thread safety, the custom epoch, the last used timestamp, the node identifier and the sequence counter.

ID generation (Generate)

func (n *Node) Generate() ID {
    n.mu.Lock()
    now := time.Since(n.epoch).Nanoseconds() / 1000000 // milliseconds since epoch
    if now == n.time { // same millisecond as previous call
        n.step = (n.step + 1) & n.stepMask
        if n.step == 0 { // sequence overflow, wait for next millisecond
            for now <= n.time {
                now = time.Since(n.epoch).Nanoseconds() / 1000000
            }
        }
    } else {
        n.step = 0 // new millisecond, reset sequence
    }
    n.time = now
    r := ID((now) << n.timeShift | (n.node << n.nodeShift) | n.step)
    n.mu.Unlock()
    return r
}

Steps performed by Generate:

Lock the node mutex ( n.mu) to guarantee exclusive access.

Compute the current timestamp in milliseconds relative to n.epoch.

If the timestamp equals the previous value, increment the sequence ( n.step) and mask it with stepMask. When the sequence wraps to zero, spin‑wait until the clock moves to the next millisecond.

If the timestamp has advanced, reset the sequence to zero.

Store the new timestamp in n.time.

Assemble the 64‑bit ID by left‑shifting the timestamp ( timeShift), left‑shifting the node ID ( nodeShift) and OR‑combining the sequence.

Unlock the mutex and return the generated ID.

Key properties

The algorithm guarantees monotonic, time‑ordered IDs as long as the system clock does not move backwards.

With 5‑bit datacenter and machine fields the maximum node identifier is 31. The 12‑bit sequence allows up to 4096 IDs per node per millisecond.

All bit‑wise operations are performed with pre‑computed masks and shift constants, making the implementation extremely fast.

Snowflake flowchart
Snowflake flowchart
concurrencySnowflakeDistributed IDunique-id-generation
Ops Development & AI Practice
Written by

Ops Development & AI Practice

DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.