Go library for generating short, unique, time-based string IDs.
Go
20
19 commits
updated Aug 17, 2026
A Go library for generating short, configurable string IDs with a time and random component. Also referred to as "FIDs": Flexible IDs.
Useful for when you want to guarantee 0 collisions between two points in time, while minimizing collisions for generated IDs within that time.
For example:
Generate base-62 IDs with a tick size of 100 milliseconds, and with 5 extra random characters at the end.
May generate J3GzHY02O6S.
go get github.com/amterp/flexid
You can use the default generator, which uses some sensible default settings.
import fid "github.com/amterp/flexid"
id := fid.MustGenerate()
anotherId, err := fid.Generate()
You can create your own Generator by passing it your own Config object.
import fid "github.com/amterp/flexid"
// NewConfig creates with defaults.
// You can then chain With methods to customize settings.
config := fid.NewConfig().
WithTickSize(fid.Second).
WithNumRandomChars(6).
WithAlphabet(fid.Base16LowerAlphabet)
// Create the generator with the config.
generator, err := fid.NewGenerator(config)
if err != nil {
panic(err)
}
// Use it to generate ids.
id := generator.MustGenerate()
It's simple!
TLDR: Given an alphabet, encode an epoch timestamp, and append random characters from the alphabet.
Every ID begins with an encoded epoch timestamp. This timestamp is measured in ticks. The tick size is configurable and defaults to one millisecond.
For example, if you specify a 100ms tick size, the time component will be the number of 100ms ticks since epoch. On every new tick, the time component changes, guaranteeing uniqueness across each increment of your tick size (100ms in this example). The larger your tick size, the shorter the time component will be when encoded (as the number of ticks since epoch will go down).
Some examples of how just this beginning segment varies depending on the tick size:
| Tick Size | Example |
|---|---|
| Millisecond | Ui8NksP |
| Decisecond (100ms) | J2YxUE |
| Second | 1u3UkZ |
| Hour | 223a |
| Day | 5Fe |
By default, the epoch start time is the traditional UNIX epoch of 1970-01-01. You can override this, however, to reduce the size of the time component.
The following table compares IDs generated off the UNIX epoch vs. a 2025-01-01 epoch, generated in 2025-04.
| Granularity | UNIX (1970) Epoch Example | 2025-01 Epoch Example | Character reduction |
|---|---|---|---|
| Millisecond | Ui8NksP | 9YGDBT | -1 |
| Decisecond (100ms) | J2YxUE | 5vCer | -1 |
| Second | 1u3UkZ | aifP | -2 |
| Hour | 223a | dC | -2 |
| Day | 5Fe | 1d | -1 |
Note, that by starting with the time component, IDs generated with the same tick size are chronologically sortable.
The time component, by itself, guarantees uniqueness between ticks. However, to avoid collisions within the same tick, we add a "random component". Simply put, we randomly select characters from a given alphabet.
Using the default base-62 as an example, each appended character reduces the likelihood of collision by a factor of 62.
If we use 5 random characters, that's 62^5 = 916,132,832 unique possibilities. So, for any two IDs generated in the same granularity tick, the odds of them
colliding is 1 in 916,132,832.
That said, be aware of the Birthday Problem and the Pigeonhole principle.
Below are some examples of IDs generated with different settings.
| Settings | Example |
|---|---|
| Base-62, UNIX epoch, millisecond, 5 random chars (default) | Ui8TX1zB2Avb |
| Base-36, UNIX epoch, millisecond, 5 random chars | m9dw96b1y9gtx |
| Base-62, UNIX epoch, decisecond, 5 random chars | J2Z31IPSxtl |
| Base-62, 2025-01 epoch, decisecond, 5 random chars | 5vHWk2ayCn |
| Base-64, UNIX epoch, hour, 5 random chars | B2TXxM3k1 |
| Base-16, UNIX epoch, millisecond, 6 random chars | 19628e9e59adc559d |
There are alternatives like UUIDs, NanoIDs, ULIDs, etc, so what do FIDs offer over these?
Generating FIDs is very fast! There's no state or locking - they'll generate as fast as your CPU can go!
Benchmarking on an Apple M2 Pro, I get ~240 nanoseconds / op, or around 4-5 million IDs per second.
Contributions are welcome! Please feel free to open an issue or submit a pull request.
This library is licensed under the MIT license.
Note: This library was previously known as stid. If you used it, see MIGRATING.md on how to migrate to flexid.
Go library for generating short, unique, time-based string IDs.
Go
20
19 commits
updated Aug 17, 2026
A Go library for generating short, configurable string IDs with a time and random component. Also referred to as "FIDs": Flexible IDs.
Useful for when you want to guarantee 0 collisions between two points in time, while minimizing collisions for generated IDs within that time.
For example:
Generate base-62 IDs with a tick size of 100 milliseconds, and with 5 extra random characters at the end.
May generate J3GzHY02O6S.
go get github.com/amterp/flexid
You can use the default generator, which uses some sensible default settings.
import fid "github.com/amterp/flexid"
id := fid.MustGenerate()
anotherId, err := fid.Generate()
You can create your own Generator by passing it your own Config object.
import fid "github.com/amterp/flexid"
// NewConfig creates with defaults.
// You can then chain With methods to customize settings.
config := fid.NewConfig().
WithTickSize(fid.Second).
WithNumRandomChars(6).
WithAlphabet(fid.Base16LowerAlphabet)
// Create the generator with the config.
generator, err := fid.NewGenerator(config)
if err != nil {
panic(err)
}
// Use it to generate ids.
id := generator.MustGenerate()
It's simple!
TLDR: Given an alphabet, encode an epoch timestamp, and append random characters from the alphabet.
Every ID begins with an encoded epoch timestamp. This timestamp is measured in ticks. The tick size is configurable and defaults to one millisecond.
For example, if you specify a 100ms tick size, the time component will be the number of 100ms ticks since epoch. On every new tick, the time component changes, guaranteeing uniqueness across each increment of your tick size (100ms in this example). The larger your tick size, the shorter the time component will be when encoded (as the number of ticks since epoch will go down).
Some examples of how just this beginning segment varies depending on the tick size:
| Tick Size | Example |
|---|---|
| Millisecond | Ui8NksP |
| Decisecond (100ms) | J2YxUE |
| Second | 1u3UkZ |
| Hour | 223a |
| Day | 5Fe |
By default, the epoch start time is the traditional UNIX epoch of 1970-01-01. You can override this, however, to reduce the size of the time component.
The following table compares IDs generated off the UNIX epoch vs. a 2025-01-01 epoch, generated in 2025-04.
| Granularity | UNIX (1970) Epoch Example | 2025-01 Epoch Example | Character reduction |
|---|---|---|---|
| Millisecond | Ui8NksP | 9YGDBT | -1 |
| Decisecond (100ms) | J2YxUE | 5vCer | -1 |
| Second | 1u3UkZ | aifP | -2 |
| Hour | 223a | dC | -2 |
| Day | 5Fe | 1d | -1 |
Note, that by starting with the time component, IDs generated with the same tick size are chronologically sortable.
The time component, by itself, guarantees uniqueness between ticks. However, to avoid collisions within the same tick, we add a "random component". Simply put, we randomly select characters from a given alphabet.
Using the default base-62 as an example, each appended character reduces the likelihood of collision by a factor of 62.
If we use 5 random characters, that's 62^5 = 916,132,832 unique possibilities. So, for any two IDs generated in the same granularity tick, the odds of them
colliding is 1 in 916,132,832.
That said, be aware of the Birthday Problem and the Pigeonhole principle.
Below are some examples of IDs generated with different settings.
| Settings | Example |
|---|---|
| Base-62, UNIX epoch, millisecond, 5 random chars (default) | Ui8TX1zB2Avb |
| Base-36, UNIX epoch, millisecond, 5 random chars | m9dw96b1y9gtx |
| Base-62, UNIX epoch, decisecond, 5 random chars | J2Z31IPSxtl |
| Base-62, 2025-01 epoch, decisecond, 5 random chars | 5vHWk2ayCn |
| Base-64, UNIX epoch, hour, 5 random chars | B2TXxM3k1 |
| Base-16, UNIX epoch, millisecond, 6 random chars | 19628e9e59adc559d |
There are alternatives like UUIDs, NanoIDs, ULIDs, etc, so what do FIDs offer over these?
Generating FIDs is very fast! There's no state or locking - they'll generate as fast as your CPU can go!
Benchmarking on an Apple M2 Pro, I get ~240 nanoseconds / op, or around 4-5 million IDs per second.
Contributions are welcome! Please feel free to open an issue or submit a pull request.
This library is licensed under the MIT license.
Note: This library was previously known as stid. If you used it, see MIGRATING.md on how to migrate to flexid.