5 min read
I hashed the parsed lockfile from a real hono checkout (1,070 packages) on this box: ohash 2.0.12 took about 3.5 ms per hash, while object-hash 3.0.0 took about 36 ms. That is roughly 10× faster, for 3.3 KB gzip instead of 11.4 KB.
Short answer: use ohash for new cache keys, memo keys and change detection. Keep object-hash only if you need its options (md5 or passthrough output, unorderedArrays), or if stored hashes would break when the format changes. No affiliate links in this post.
| Input (median of 9, 2 runs) | ohash 2.0.12 | object-hash sha1 | object-hash md5 |
|---|---|---|---|
| Lockfile object, 1,070 packages | 3.59 / 3.45 ms | 36.3 / 35.8 ms | 37.4 / 36.4 ms |
| Small config, ops per second | 117,650 / 207,187 | 11,346 / 14,013 | 13,626 / 13,941 |
Yes, about 10× on the big object and 10–15× on the small one. The small-config number for ohash moved a lot between runs (JIT warm-up order), so read it as "an order of magnitude", not an exact figure. If you hash query keys on every render, compare this with how TanStack Query vs SWR build their keys.
| Build (esbuild browser minify) | Minified | gzip -9 |
|---|---|---|
| ohash hash() | 6,867 B | 3,284 B |
| object-hash (browser build) | 35,284 B | 11,426 B |
About 3.5× smaller. object-hash's browser field points to a prebuilt file that brings its own crypto code, so tree-shaking cannot trim it. ohash ships a small JavaScript SHA-256 for browsers and uses node:crypto on Node; I checked that both paths gave the same digest for the same input. Track the difference in CI with size-limit vs bundlesize.
| Case | ohash | object-hash |
|---|---|---|
| Same keys, different key order | equal | equal |
| Arrays [1,2] vs [2,1] | different | different (equal with unorderedArrays) |
| { b: undefined } vs key missing | different | different |
| Same Date instant | equal | equal |
| Date vs its ISO string | different | different |
| Map or Set in a different order | equal | equal |
| Class instance vs plain object | different | different |
| 1 vs "1" | different | different |
| Output for { a: 1 } | 43-char base64url SHA-256 | 40-char hex SHA-1 |
In 10 edge-case comparisons they agreed every time. What differs is the output: the strings look nothing alike, so switching libraries changes every stored hash. Plan one cache flush. For copying the objects you hash, see klona vs structuredClone.
| Situation | My pick |
|---|---|
| New cache or memo keys | ohash |
| Browser bundle, size matters | ohash |
| Hashes already stored in a DB or CDN keys | stay on object-hash, or migrate with one flush |
| Need md5, passthrough or unorderedArrays | object-hash |
| Only need deep equality, no hash string | ohash isEqual |
Bottom line: ohash was about 10× faster and 3.5× smaller than object-hash on this box, with the same results on every edge case I tried. Who should not switch: systems that compare against hashes saved earlier and cannot rebuild them. If ohash replaces a lodash-style helper too, the es-toolkit vs lodash numbers show the same pattern of small modern packages. Content-hash caching across a monorepo is a different problem; see Turbo vs Nx.
How this was made: I installed both packages on the ShopperCove box, hashed a small config and a real parsed lockfile in two order-reversed runs, measured esbuild bundle sizes, and ran 10 edge-case comparisons plus a Node-vs-browser digest check. The JSON results are saved next to the scripts. The write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~01:35 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). ohash 2.0.12 vs object-hash 3.0.0, esbuild 0.28.2 browser ESM minify + gzip -9. Fixture /workspace/bench-ohash-object-hash/. Inputs: small nested config and hono pnpm-lock.yaml main document via js-yaml loadAll (1,070 packages, 1,075 snapshots, 313,361 chars JSON). Speed (9-round median, order-reversed): lock ohash 3.593 / 3.454 ms; object-hash sha1 36.307 / 35.821 ms; md5 37.358 / 36.353 ms. Small config ohash 117,650 / 207,187 ops/s; object-hash sha1 11,346 / 14,013; md5 13,626 / 13,941. Size: ohash 6,867/3,284 B vs object-hash browser dist 35,284/11,426 B. Behavior (res-behavior.json): key order ignored by both; array order, undefined-vs-missing, Date-vs-ISO, class-vs-plain, 1-vs-"1", different functions all differ in both; Map/Set order ignored by both; ohash JS SHA-256 equals node:crypto digest; object-hash unorderedArrays works. Not tested: browsers, Bun/Deno, circular refs, typed arrays, collision quality. No affiliate.
Hands-on6 Oct 20264 min read
Hands-on6 Oct 20265 min read
zod 4.6.5 vs valibot 1.5.0 on one nested user schema (uuid/email/age/tags/profile/enum). Warm medians after 2k warmup + 9×50k: zod.parse 1,144,494 ops/s vs valibot.parse 968,187 (~1.18×). safeParse fail: valibot 697,760 vs zod 415,523 (~1.7×). esbuild minify+gzip9: 452,999/92,636 B vs 85,126/15,422 B. Package trees ~6.1 MB vs ~1.9 MB. Fixture /workspace/bench-zod-valibot/.
Hands-on6 Oct 20264 min read