Javid
·12 min read

Snowflake ID: Decode Twitter, Discord and Instagram IDs

SelfDevKit ID Analyzer, an offline ID decoder, showing the type and alternative representations of a pasted ID

What is a Snowflake ID?

A Snowflake ID is a 64-bit integer ID that starts with a millisecond timestamp, followed by a machine ID and a per-machine sequence number. Because the time sits in the high bits, Snowflake IDs sort by creation time, and anyone who knows the layout can read the creation time back out of the number.

Twitter announced Snowflake in June 2010 to generate tweet IDs across many servers with no central counter. Discord, Instagram, Mastodon, and Sony all use the same idea. They do not use the same layout, though, and that is where decoding goes wrong: one 18-digit number decodes to a different date under each platform's epoch and bit split.

This guide covers the layouts, then the code to decode and generate a Snowflake ID. Every snippet was run before publishing (Node 24, Python 3.14, SQLite, and PostgreSQL 14), and every output shown is the real result.

How a Snowflake ID is built

A Snowflake ID packs three fields into a signed 64-bit integer. In Twitter's original layout, from its IdWorker.scala, the bits are:

0 | 41 bits: ms since epoch | 5 bits: datacenter | 5 bits: worker | 12 bits: sequence
  • Sign bit. Always 0, so the ID stays positive in languages with signed 64-bit integers.
  • Timestamp. Milliseconds since a custom epoch. Twitter's is 1288834974657, which is 2010-11-04 at 01:42:54.657 UTC.
  • Machine ID. 10 bits, split by Twitter into a 5-bit datacenter ID and a 5-bit worker ID. That allows 1,024 generators.
  • Sequence. A 12-bit counter that resets every millisecond, so one generator can issue 4,096 IDs per millisecond.

The timestamp sits in the most significant bits, so a larger ID means a later ID. Twitter's announcement called the result "roughly sortable" (k-sorted): IDs from different machines within the same second can interleave, but over any longer span they sort in time order.

Take the tweet ID Wikipedia uses as its example, 1888944671579078978. Shift it right by 22 bits, add the epoch, and you get 2025-02-10T13:34:39.256Z. The machine bits give 360 and the sequence bits give 322.

Forty-one bits of milliseconds last about 69.7 years. Twitter-layout IDs run out in July 2080.

Snowflake ID layouts by platform

Every Snowflake variant puts a timestamp at the top, but each one picks its own epoch, time unit, and order for the low fields. These are the published layouts:

Platform Timestamp Epoch Lower bits Timestamp formula
Twitter / X 41 bits, ms 1288834974657 (2010-11-04) 10 machine, 12 sequence (id >> 22) + 1288834974657
Discord 42 bits (63 to 22), ms 1420070400000 (2015-01-01) 5 worker, 5 process, 12 increment (id >> 22) + 1420070400000
Instagram 41 bits, ms 1314220021721 (2011-08-24) 13 shard ID, 10 sequence (id >> 23) + 1314220021721
Sonyflake (v1 defaults) 39 bits, 10 ms units 2014-09-01 8 sequence, then 16 machine (id >> 24) * 10 ms + 2014-09-01
Mastodon 48 bits, ms Unix epoch 16 bits of hashed sequence id >> 16

Sources: Discord API reference, Sharding & IDs at Instagram (the our_epoch value comes from its PL/pgSQL), the Sonyflake repository, and Mastodon's snowflake.rb.

A few details matter when you decode:

  • Discord and Twitter shift by the same 22 bits. Discord counts the top bit as part of its 42-bit timestamp, while Twitter leaves it as an unused sign bit. Only the epoch differs.
  • Sonyflake reverses the low fields. The sequence comes before the machine ID, and time counts in 10 ms steps. Sonyflake v2 changed the default start time to 2025-01-01 and lets you configure the bit widths and time unit, so check the generator's settings before decoding.
  • Mastodon has no custom epoch. The top 48 bits are plain Unix milliseconds. The low 16 bits are a hash plus a counter, chosen to keep IDs unique within a millisecond "yet obscure the 'serial number' of this row," according to the source comment.

A bare number does not tell you which layout produced it. Decode a Discord ID with Twitter's epoch and you get a date about four years too early, with no error. Always decode by where the ID came from.

How to decode a Snowflake ID timestamp

To decode a Snowflake ID, shift it right past the low fields, add the platform's epoch, and read the result as Unix milliseconds. Use 64-bit integer math, never floating point.

In JavaScript that means BigInt, and the ID has to start as a string (the next section explains why):

const LAYOUTS = {
  twitter:   { epoch: 1288834974657n, shift: 22n },
  discord:   { epoch: 1420070400000n, shift: 22n },
  instagram: { epoch: 1314220021721n, shift: 23n },
};

function snowflakeTime(id, layout) {
  const { epoch, shift } = LAYOUTS[layout];
  const ms = (BigInt(id) >> shift) + epoch;
  return new Date(Number(ms)).toISOString();
}

snowflakeTime('1888944671579078978', 'twitter'); // '2025-02-10T13:34:39.256Z'
snowflakeTime('175928847299117063', 'discord');  // '2016-04-30T11:18:25.796Z'

The second ID is the example from Discord's documentation. Pulling out every field uses the masks from the same page:

function decodeDiscord(id) {
  const n = BigInt(id);
  return {
    timestamp: new Date(Number((n >> 22n) + 1420070400000n)).toISOString(),
    workerId: Number((n & 0x3E0000n) >> 17n),
    processId: Number((n & 0x1F000n) >> 12n),
    increment: Number(n & 0xFFFn),
  };
}

decodeDiscord('175928847299117063');
// { timestamp: '2016-04-30T11:18:25.796Z', workerId: 1, processId: 0, increment: 7 }

Python integers have arbitrary precision, so the shifts work directly on the number. This covers all five layouts:

from datetime import datetime, timedelta, timezone

UTC = timezone.utc

def unix_ms(ms):
    return datetime.fromtimestamp(ms / 1000, UTC).isoformat(timespec="milliseconds")

def twitter(i):
    return unix_ms((i >> 22) + 1288834974657), (i >> 12) & 0x3FF, i & 0xFFF

def discord(i):
    return unix_ms((i >> 22) + 1420070400000), (i >> 17) & 0x1F, (i >> 12) & 0x1F, i & 0xFFF

def instagram(i):
    return unix_ms((i >> 23) + 1314220021721), (i >> 10) & 0x1FFF, i & 0x3FF

def mastodon(i):
    return unix_ms(i >> 16), i & 0xFFFF

def sonyflake_v1(i):
    start = datetime(2014, 9, 1, tzinfo=UTC)
    t = start + timedelta(milliseconds=(i >> 24) * 10)
    return t.isoformat(timespec="milliseconds"), (i >> 16) & 0xFF, i & 0xFFFF

print(mastodon(103270115826048975))
# ('2019-12-08T03:48:33.849+00:00', 40911)

That Mastodon ID is the example status in Mastodon's API docs. It decodes to 52 ms before the created_at value printed next to it (03:48:33.901Z), because the ID comes from the database clock (now(), the start of the transaction) while the application sets created_at on its own. Treat a decoded time as accurate to a fraction of a second, not as the official timestamp.

Databases can decode in SQL. PostgreSQL:

SELECT to_timestamp(((1888944671579078978 >> 22) + 1288834974657) / 1000.0) AT TIME ZONE 'UTC';
-- 2025-02-10 13:34:39.256

SQLite:

SELECT strftime('%Y-%m-%dT%H:%M:%fZ', ((1888944671579078978 >> 22) + 1288834974657) / 1000.0, 'unixepoch');
-- 2025-02-10T13:34:39.256Z

The same math runs in reverse, which is how you query by time without a created_at column. Shift a timestamp left to get the smallest possible ID for that moment, then filter on the primary key. Discord's docs recommend this for pagination. Every Discord ID from 2016-05-01 onward is at least:

SELECT ((extract(epoch FROM timestamptz '2016-05-01 00:00:00+00') * 1000)::bigint - 1420070400000) << 22;
-- 176120502681600000

So WHERE id >= 176120502681600000 selects everything from May 2016 onward, using the existing index. For more on epochs and units, see the timestamp converter guide.

The JavaScript precision trap

JavaScript numbers are 64-bit floats with 53 bits of integer precision, so JSON.parse silently rounds any Snowflake ID above 2^53 - 1. Nearly every real Snowflake ID is above that limit. Here is Discord's documented ID going through a plain parse:

const parsed = JSON.parse('{"id": 175928847299117063}').id;
parsed;                       // 175928847299117060
Number.isSafeInteger(parsed); // false
BigInt(parsed);               // 175928847299117056n

The value now in memory is ...056, not ...063. The timestamp bits survive, because they sit at the top, but the low bits do not. Decode the printed value and the increment comes back as 4 instead of 7. Worse, JSON.parse('{"id": 175928847299117060}') produces exactly the same number, so two different IDs become equal and lookups, joins, and deduplication quietly break.

That is why Discord's API returns every ID as a string, and why X's v1.1 API sent both id and id_str. The fixes:

  • Keep IDs as strings in JSON, in JavaScript state, and in URLs. Convert to BigInt only to do math.
  • Read raw numbers with the reviver's source text when you cannot change the API. The third context argument to the reviver carries the original digits. It works in Chrome 114, Firefox 135, Safari 18.4, and Node 21 and later:
JSON.parse('{"id": 175928847299117063}', (key, value, ctx) =>
  key === 'id' ? BigInt(ctx.source) : value);
// { id: 175928847299117063n }

The same trap affects any large integer, not only Snowflakes. The JSON validator guide covers it alongside other parse-time surprises.

How to generate Snowflake IDs

To generate Snowflake IDs, each generator needs a unique machine ID, a clock, and a sequence counter that resets every millisecond. A minimal Twitter-layout generator in Python:

import threading
import time

class Snowflake:
    EPOCH = 1288834974657  # pick your own epoch once and never change it

    def __init__(self, machine_id):
        if not 0 <= machine_id < 1024:
            raise ValueError("machine_id must fit in 10 bits")
        self.machine_id = machine_id
        self.last_ms = -1
        self.seq = 0
        self.lock = threading.Lock()

    def next_id(self):
        with self.lock:
            now = time.time_ns() // 1_000_000
            if now < self.last_ms:
                raise RuntimeError(f"clock moved back {self.last_ms - now} ms")
            if now == self.last_ms:
                self.seq = (self.seq + 1) & 0xFFF
                if self.seq == 0:  # all 4,096 sequence values used
                    while now <= self.last_ms:
                        now = time.time_ns() // 1_000_000
            else:
                self.seq = 0
            self.last_ms = now
            return ((now - self.EPOCH) << 22) | (self.machine_id << 12) | self.seq

In one test run, generating 100,000 IDs with this class in a tight loop produced 100,000 unique values in strictly increasing order. They spanned 25 milliseconds, and in 21 of them the sequence hit its 4,096 cap. A single Python process can outrun the sequence, so the wait-for-the-next-millisecond branch is not theoretical.

Two failure modes need a decision up front:

  1. The clock moves backwards. NTP corrections and VM migrations do this. Twitter's original generator refused to issue IDs, throwing "Clock moved backwards. Refusing to generate id for %d milliseconds". Sonyflake keeps using its last time value and keeps counting, borrowing time ahead and sleeping when the sequence runs out. Refusing is safer for uniqueness. Borrowing keeps the service up.
  2. Two generators share a machine ID. Then nothing prevents duplicates. Assign machine IDs from configuration, a coordination service, or something unique per host. Sonyflake defaults to the lower 16 bits of the private IP address.

If you need IDs inside PostgreSQL rather than in application code, Instagram's PL/pgSQL function is a good model: it builds the ID from clock_timestamp(), a shard number, and nextval() modulo 1024.

Snowflake ID vs UUID v7 vs ULID

A Snowflake ID is half the size of a UUID v7 or ULID, but it needs machine ID coordination that the other two avoid. All three sort by creation time.

Snowflake ID UUID v7 ULID
Size 64 bits 128 bits 128 bits
Database type bigint uuid text, or converted to uuid
Text form Up to 19 digits 36 hex characters with hyphens 26 Base32 characters
Uniqueness from Machine ID + sequence 74 random bits 80 random bits
Coordination Each generator needs a unique machine ID None None
Safe as a JS number No, use strings Always a string Always a string

Pick Snowflake when ID size matters at scale, such as huge tables, indexes, and foreign keys, and you can manage machine IDs. Pick UUID v7 or ULID when you want generators to work with no setup. The ULID guide compares ULID and UUID v7 in detail, and the UUID generator guide covers the wider field.

What a Snowflake ID reveals

Every Snowflake ID reveals its creation time to the millisecond, and usually which machine made it. Collect a few public IDs and the sequence values hint at volume. That is why Mastodon hashes its low bits.

A Snowflake ID is also predictable: the next ID from a generator is the current one plus a small amount. Never use one as a session token, password-reset link, or API key.

Decoding Snowflake IDs offline

SelfDevKit's ID Analyzer decodes IDs on your machine, which matters when the ID comes from a production log. Paste a numeric ID and it lists its readings under several Snowflake layouts, each with a timestamp, worker ID, and sequence, plus the number in hex. Use its Twitter/X reading for tweet IDs and its Instagram reading for Instagram-style IDs; both follow the layouts above. For Discord, Sonyflake, and Mastodon IDs, use the snippets in this post, which follow each platform's published layout. The same tool reads ULIDs, UUIDs, and KSUIDs.

SelfDevKit ID Analyzer, which also decodes Snowflake IDs, showing the type and alternative representations of a UUID v7

Download SelfDevKit to inspect IDs, timestamps, and 50+ other developer formats offline on macOS, Windows, and Linux.

Related Articles

ULID: What It Is, How It Sorts, and Where It Breaks
DEVELOPER TOOLS

ULID: What It Is, How It Sorts, and Where It Breaks

ULID explained: the 128-bit sortable ID, how to decode its timestamp, and the ordering, parsing, and storage traps that show up in real libraries.

Read →
UUID Generator: How to Create Unique IDs (v4, v7, ULID, and More)
DEVELOPER TOOLS

UUID Generator: How to Create Unique IDs (v4, v7, ULID, and More)

Generate UUIDs and unique IDs for your projects. Learn which version to use, how they work, and why UUID v7 is replacing v4.

Read →
Timestamp Unix Converter: How to Pick One and Prove It Works
DEVELOPER TOOLS

Timestamp Unix Converter: How to Pick One and Prove It Works

A timestamp unix converter is easy to find and easy to trust. See how five tools disagree on one input, plus a test suite to check yours.

Read →