ajv 8.20.0 (+ajv-formats 3.0.1) vs zod 4.6.5 on the same nested user shape as zod-vs-valibot. Warm medians (2k warmup + 9x50k): Ajv validate ok 3,933,337 ops/s vs zod.safeParse 1,138,406 (~3.5x). Bad input: Ajv allErrors 4,094,452 vs Zod 435,537 (~9.4x). Setup: Ajv new+addFormats+compile median 6.47 ms vs Zod build 0.17 ms; break-even ~10k validations. Browser gzip: Ajv runtime 41 KB, Ajv standalone 1.8 KB, Zod classic 93 KB, zod/mini 6.8 KB. Fixture /workspace/bench-ajv-zod/.
6 min read
I validated one nested user object 50,000 times per round on this box: Ajv 8.20.0 median 3.93M ops/s, Zod 4.6.5 safeParse median 1.14M ops/s, about 3.5× in Ajv's favor. The catch is setup: compiling the Ajv schema took a median 6.5 ms against 0.17 ms to build the Zod schema.
Short answer: pick Ajv for long-running Node services that validate JSON Schema at high volume. Stay on Zod for TypeScript-first code, short-lived functions and browser forms. No affiliate links in this post.
new Ajv() + addFormats + compile and of the Zod z.object.| Path | Median ops/s | Median ms / 50k |
|---|---|---|
| Ajv validate, good input | 3,933,337 | 12.7 |
| zod.safeParse, good input | 1,138,406 | 43.9 |
| zod.parse, good input | 1,075,899 | 46.5 |
| Ajv validate, bad input (first error) | 6,664,006 | 7.5 |
| Ajv validate, bad input (allErrors) | 4,094,452 | 12.2 |
| zod.safeParse, bad input | 435,537 | 114.8 |
Ajv compiles each schema into a plain JavaScript function, so the hot path is a few type checks and regex tests. Zod walks its schema objects on every call. If you are choosing between Zod and the lighter TypeScript-first option instead, my Zod vs Valibot numbers use the same user shape.
Compile time is the part most benchmarks skip. Over 30 fresh builds, Ajv setup had a median of 6.47 ms (range 5.7–11.2 ms) and Zod 0.17 ms. The very first Ajv compile in a cold process took 32.5 ms against 7.4 ms for the first Zod build plus parse.
At my measured speeds, Ajv saves about 0.62 µs per valid object. That pays back the 6.3 ms setup gap after roughly 10,000 validations. A serverless function that checks a few hundred requests per cold start comes out ahead on Zod. A long-running API server passes that point within seconds. Compile once at module load and reuse the validator. Never compile inside a request handler.
| Browser build (esbuild minify, ESM) | Minified | gzip -9 |
|---|---|---|
| Ajv runtime + ajv-formats + compile | 134,306 B | 41,149 B |
| Ajv standalone (precompiled validator only) | 6,628 B | 1,780 B |
| Zod 4 classic, this schema | 453,301 B | 92,748 B |
| zod/mini, this schema | 20,242 B | 6,755 B |
In the browser, Ajv standalone was the smallest by far: 1.8 KB gzip, because the validator is generated at build time. Runtime Ajv also calls new Function, which a strict Content-Security-Policy without unsafe-eval blocks. Standalone code avoids that too. Zod classic shipped 93 KB gzip for this one schema. zod/mini cut that to 6.8 KB. Installed trees were about 1.35 MB for Ajv, ajv-formats and their 4 dependencies against 6.1 MB for Zod. For tracking shipped weight in CI, see size-limit vs bundlesize.
| Situation | My pick |
|---|---|
| Node API validating JSON bodies at volume | Ajv, compiled once at startup |
| You already publish JSON Schema or OpenAPI | Ajv |
| TypeScript-first app wanting inferred types | Zod |
| Short-lived serverless function | Zod (setup is 0.17 ms vs 6.5 ms) |
| Browser form validation | Ajv standalone or zod/mini |
| Strict CSP with no unsafe-eval | Ajv standalone or Zod |
Types are the real trade-off. Zod gives you z.infer for free. With Ajv you either write JSONSchemaType<T> by hand or generate types from the schema. If your validators run in tests, my Jest vs Vitest run covers the runner side. If a swap leaves the old library installed, knip vs depcheck finds it. For linting the schema files themselves, see Biome vs ESLint/Prettier.
The format checks are not identical. ajv-formats and Zod use different email and uuid patterns, so test your own edge cases before switching.
Bottom line: on 50k validations of one user object, Ajv 8.20.0 hit 3.93M ops/s against 1.14M for Zod 4.6.5, but its 6.5 ms compile only pays off after about 10,000 calls. Who should not switch: TypeScript teams whose Zod schemas double as their types and whose validation is nowhere near a hot path.
How this was made: I installed Ajv and Zod on the ShopperCove box, timed warm validation rounds and fresh schema builds, measured esbuild minify+gzip sizes and recorded the JSON. The write-up was drafted with AI help and checked against that output.
Lab evidence
Hands-on on ShopperCove box 6 Oct 2026 ~00:36-00:38 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 22.20.0). ajv 8.20.0 + ajv-formats 3.0.1 vs zod 4.6.5. Fixture /workspace/bench-ajv-zod/: one user schema (uuid, email, int age 0-120, tags[], nested profile, enum role) as JSON Schema and as z.object; good + bad payloads. 2,000 warm calls then 9 rounds x 50,000. Medians: Ajv ok 3,933,337 ops/s (12.7 ms/50k); zod.safeParse ok 1,138,406 (43.9 ms); zod.parse ok 1,075,899; Ajv fail first-error 6,664,006; Ajv fail allErrors 4,094,452; zod.safeParse fail 435,537. Both report 3 errors on bad payload. Setup (30 fresh builds): Ajv 6.474 ms median (5.7-11.2), Zod 0.174 ms; cold first Ajv compile 32.5 ms vs Zod first build+parse 7.4 ms. esbuild minify/gzip9: Ajv runtime+formats+compile 134,306/41,149 B; Ajv standalone 6,628/1,780 B; Zod classic schema 453,301/92,748 B; zod/mini schema 20,242/6,755 B. Disk ~1.35 MB vs ~6.1 MB. Raw: res-ajv-zod.json + res-bundle.json. Not tested: JTD, async/custom keywords, Zod transforms, TS compile time, Bun/Deno, edge. No affiliate.