hyperid: decoding a unique id back into a uuid and a counter
Uber-fast unique id generation, for Node.js and the browser
At a glance
- What is it?
- hyperid is a small MIT licensed JavaScript library that generates unique ids by combining a uuid with an incrementing counter, which makes an id decodable into its two parts again. The author's own benchmarks put it far ahead of uuid.v4 and nanoid, and the counter is capped so it stays a V8 small integer.
- Who is it for?
- hyperid is worth using when you generate ids at a rate where the format itself has to carry structure, because a counter rather than random bytes means the ids are ordered, compact and decodable, and the author benchmarks put it well above uuid.v4 and nanoid on throughput.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 98 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An id is a uuid and a counter, which is why it can be decoded
hyperid is described as uber-fast unique id generation for Node.js and the browser, and the design decision that defines it is that the id is not an opaque blob. It is two pieces of data glued together, a uuid and a counter, and the library can take them apart again.
The decode function returns exactly those two components:
{
uuid: '049b7020-c787-41bf-a1d2-a97612c11418',
count: 1
}The uuid identifies the process or instance that issued the id, and the counter counts the ids that instance has produced. That is the whole scheme, and it explains every other property of the library.
It explains why the ids are ordered, since a counter increments, and why they are short, since a counter is more compact than the bytes of a random identifier. It also explains the sort of thing that gets said about them, which is that they leak information: an id tells you roughly when it was issued and how many ids came before it.
The API is small. The hyperid function takes an optional fixedLength flag or an options object and returns a generator function. The generator instance returns an id on each call, and the generator exposes the uuid it is currently using as instance.uuid. The decode function is also aliased as instance.decode, so the same generator can both produce and interpret ids.
The package is MIT licensed, written in JavaScript, and the top-level source layout is telling: hyperid.js holds the core, uuid-node.js and uuid-browser.js hold the platform-specific uuid generation, and the browser field in package.json swaps the node variant for the browser one at bundle time. There is a TypeScript declaration file, index.d.ts, so the package is typed for consumers even though it is written in plain JavaScript.
The counter is capped so it stays a V8 small integer
The performance claim rests on a specific implementation detail that the README is unusually willing to explain.
The maxInt option controls when the id is regenerated. The README says the lesser the maxInt, the higher the performance because of SMI, a V8 optimization, and that when maxInt is not provided the uuid is regenerated every Math.pow(2, 31) - 1, in order to keep the integer a small integer.
Small integers in V8 are a separate representation from doubles, and they are cheaper to work with. If the counter overflowed into a double, every increment would be an allocation rather than a pointer bump, and the whole point of the design would be lost. Capping the counter at the small integer range and rotating the uuid instead keeps the hot path on the fast representation.
That is why the max int variant tops the benchmark table, since a smaller counter range means the counter is more likely to stay in the smallest representation, and why the number 2147483647 shows up as the upper bound for the startFrom option, with fractions discarded and only the integer part mattering.
The startFrom option controls where the counter begins, which is useful when you want ids from a specific system to sort after ids from another, or when you are migrating and want a new deployment to start above the ids an old one issued.
The other options are about the shape of the string. fixedLength, either as a boolean or as { fixedLength: true }, makes every id exactly 33 characters long, which is the default off. The urlSafe option generates ids that are safe per RFC4648, and the README notes that if you generate url safe ids you must pass { urlSafe: true } when decoding, since the decoding has to know which alphabet to interpret.
Four knobs, each with a reason, and the interaction between maxInt and fixedLength is where the performance lives.
The benchmark table is the project's main argument
The README leads with a benchmark table rather than with installation, which tells you what the author considers the point of the library.
The numbers were taken on an Apple M4 Max with Node.js v24.13.0, and the README says how many runs were sampled for each entry. hyperid with max int is reported at 99,237,132 ops per second, the fixed length variants in the low sixty millions, and among the competitors uuid.v4 is at 21,321,246, napiRsUuid.v4 at 9,432,514, nanoid at 8,810,885, crypto.randomUUID at 35,347,606, and crypto.random at 1,358,283. shortid is at 1,391,482 and nid at 1,776,272, and the slowest entry is hashids using process.hrtime at 830,101.
Two things follow from this table. First, hyperid is not competing on randomness, it is competing on throughput, and it wins by a wide margin against every alternative in the list, including the native crypto UUID generator. Second, the ranking is roughly a measure of how much work each approach does per id, and hyperid is doing the least work because it is incrementing an integer.
These are the maintainer's own numbers on his own hardware, with the sampling counts published alongside them, which is at least honest about the conditions. They are not a general claim about any machine, and the pattern rather than the absolute figure is the transferable part.
The benchmark file is in the repository as benchmark.js, and the devDependencies list gives away what it compares: the uuid package, napi-rs uuid, nanoid, shortid, nid and hashids, plus the benchmark library itself and a bloomfilter package, which is presumably used to check uniqueness across a large sample rather than merely to count.
Ordering across processes is the property to think about
The id format makes two guarantees easy to state and easy to misuse.
Within one instance, the counter increases, so ids issued by that instance are ordered by issue order. Across instances, the ids are not globally ordered, because each instance has its own counter and its own uuid, and there is no shared clock or shared sequence between them. The uuid portion changes over time, which the README is explicit about: instance.uuid is the uuid used to generate the ids, it will change over time, and it is regenerated either every maxInt or every Math.pow(2, 31) - 1 ids.
That regeneration is the mechanism that keeps ids unique across instances, and it is also the thing to plan for. An id issued at counter 5 under uuid A and an id issued at counter 5 under uuid B are different ids, and once the uuid rotates, the ids issued before and after the rotation point at different uuids. If you store ids and later want to know which issuing instance produced one, you need the uuid stored alongside, and you need to accept that the uuid is a rotating label rather than a stable identity for a process.
The counter is also a disclosure. An id carries a number that tells you how many ids that instance had produced when the id was issued. For a system where ids are handed to end users, that is a small amount of information about your volume and about the order in which accounts were created, and it is worth deciding deliberately whether that matters in your context rather than discovering it later.
The counter and the uuid together also mean ids from different instances cannot be compared numerically to establish global order, which is a limitation if you need a total order across several processes and are the reason to keep a separate sequence at the storage layer.
Installing and using it takes a require and a call
Installation is a single npm command:
npm i hyperid --saveThe usage example in the README shows the whole API surface in a few lines. You require the module, call hyperid() to create an instance, and then call that instance to get an id:
'use strict'
const hyperid = require('hyperid')
const instance = hyperid()
const id = instance()
console.log(id)
console.log(instance())
console.log(hyperid.decode(id))
console.log(hyperid.decode(instance()))The last two lines are the interesting ones, because they show the round trip. The module-level hyperid.decode and the instance-level instance.decode do the same thing, and decoding an id you just generated gives you back the uuid and the count.
The package.json shows how the project is tested. The test script runs eslint first, then a tape suite over test/test.js, test/uniqueness.js and test/buffer.js piped to tap-dot, and then a TypeScript compilation of the test project. There is a separate legacy script that runs only the first tape file, and a lint:fix script. The presence of test/uniqueness.js is a hint about what the library is really about: the counter is only unique because the uuid changes when it rolls over, and the test suite is there to prove that the rollover happens correctly.
The buffer dependency in the dependencies section is there for the browser case, and the overrides field pins trim to a specific version, which is the sort of thing that shows up when a transitive dependency has had a problem.
The package is on version 4.0.0, with v3.3.0 from August 2024 and v3.2.0 from January 2024 before it, so the major version has moved on since the previous year. The last push was on 2026-07-17 and the repository is not archived.
Editorial conclusion
hyperid is worth using when you generate ids at a rate where the format itself has to carry structure, because a counter rather than random bytes means the ids are ordered, compact and decodable, and the author benchmarks put it well above uuid.v4 and nanoid on throughput. It is the wrong tool when you need ids that are unpredictable, since a counter is guessable by construction and says something about how many ids you have issued, and it is the wrong tool when you want a standard format, since these are not UUIDs despite carrying a uuid inside. Before you depend on it, verify three things yourself: that the counter rollover behaviour is acceptable for your system, since the id is regenerated every maxInt or every Math.pow(2, 31) - 1 ids by default, that the ordering guarantee holds across processes, since the counter is per instance, and that the uuid inside the id is regenerated on your schedule, because instance.uuid changes over time and any id issued before a change points at a different uuid than one issued after.
Frequently asked questions
What is hyperid and how does it generate ids?
hyperid is a JavaScript library for fast unique id generation in Node.js and the browser. An id combines a uuid identifying the generating instance with an incrementing counter, which is why the id can be decoded back into those two parts.
How do I decode a hyperid back into its components?
Use hyperid.decode(id), which is also aliased as instance.decode on a generator instance. It returns an object with a uuid and a count, and if you generated url safe ids you must pass { urlSafe: true } so the decoder knows which alphabet to use.
What does the maxInt option do in hyperid?
It sets how many ids are generated before the uuid is regenerated. The README says a lesser maxInt gives higher performance because of SMI, a V8 optimization, and by default the uuid is regenerated every Math.pow(2, 31) - 1 to keep the counter a small integer.
How do I install and use hyperid?
Install it with npm i hyperid --save, then require the module and call hyperid() to create an instance, and call that instance to obtain an id. Options include fixedLength for ids of exactly 33 characters, urlSafe for RFC4648 safe ids, and startFrom to choose the initial counter value.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mcollina-hyperid)Was this useful?