Hey everyone! 👋

I'm Dmitry, the creator of Sury—the fastest schema library out there. If you’re a fan of Zod (and who isn’t?), you’ll want to read this. Today, I want to share some surprising findings about Zod v4’s performance, what it means for you, and how to avoid the pitfalls.

Zod v4: 17x Slower? Not Quite, But...

Let’s start with a little clickbait:

Zod v4 became 17 times slower, and nobody noticed 🙈

This is 100% true, but of course, that’s not the whole story. Let’s dig in.

Recently, while prepping for the big Sury v10 release, I decided to rerun my benchmarks with Zod v4. The results? Fascinating.

  • For a non-trivial schema, Zod v4 is now 8x faster than before.
  • But when you create a schema and use it just once (a common pattern in React components), performance drops significantly—down to about 6 ops/ms.

You might think, “6 ops/ms is still fast!” But in UI-heavy apps, every millisecond counts. If you’re using Zod in your React components, this could mean a noticeable performance hit.

Schema from the benchmark

import { z } from "zod"; // 13.5 kB (min + gzip)

const zodSchema = z.object({
  number: z.number(),
  negNumber: z.number(),
  maxNumber: z.number(),
  string: z.string(),
  longString: z.string(),
  boolean: z.boolean(),
  deeplyNested: z.object({
    foo: z.string(),
    num: z.number(),
    bool: z.boolean(),
  }),
});

What Changed in Zod v4?

My hunch was that Zod v4 started using eval (or, more precisely, JIT compilation via new Function) for validation. This isn’t a bad thing — libraries like TypeBox, ArkType, and even Sury use similar techniques for speed.

But there’s a tradeoff:

  • Schema creation becomes slower (because of the JIT compilation step).

Although, it's not a problem for most users, because:

  • Validation becomes much faster (once the schema is compiled and used multiple times).

Zod v3 was already a bit slow at schema creation, but v4 takes it further. If you’re creating schemas on the fly (again, think React), you might feel the slowdown.

Is Eval/JIT Bad? Not Really.

There’s a common myth that eval/new Function is always slow or unsafe. In reality, when used carefully, it can unlock incredible performance. Sury leans heavily on JIT compilation and is still almost on par with Valibot, which is designed for quick initialization.

Here’s a quick comparison (min + gzip):

Library Import Size Parse (same schema) Create & Parse Once
Sury 4.27 kB 94,828 ops/ms (JIT only) 166 ops/ms
Zod v3 13.5 kB 1,191 ops/ms (no JIT) 93 ops/ms
Zod v4 13.5 kB 8,437 ops/ms 6 ops/ms
Valibot 1.23 kB 1,721 ops/ms (no JIT) 287 ops/ms
TypeBox 22.8 kB 99,640 ops/ms (only assert support) 111 ops/ms
ArkType 45.8 kB 67,552 ops/ms 11 ops/ms

Full comparison in Sury’s README

What Should Zod Users Do?

If you’re using Zod in a backend or for long-lived schemas, you’ll love the new speed. But if you’re creating schemas dynamically (like in React components), you might want to benchmark your app or consider alternatives.

Zod v4 does offer both normal and JIT-optimized parsing, so you might be able to tweak your usage. But it’s worth being aware of the tradeoffs.

Also, there should be a way to disable JIT compilation for those who don't want it. I don't know the exact API or whether it's exposed, but I've seen that this option exists in Zod's internals.

Enter Sury: The Fastest Schema Library

Okay, shameless plug time! 😅

I built Sury to solve exactly these problems:

  • Blazing fast parsing and validation (thanks to JIT)
  • Tiny bundle size and tree-shakable API
  • Great TypeScript inference and developer experience
  • Standard Schema and JSON Schema support out of the box
  • Declarative transformations and automatic serialization

Sury is already used in production by many companies and is compatible with tools like tRPC, TanStack Form, Hono, and more.

Here’s what using Sury looks like:

import * as S from "sury";

const filmSchema = S.schema({
  id: S.bigint,
  title: S.string,
  tags: S.array(S.string),
  rating: S.union(["G", "PG", "PG13", "R"]),
});
// On hover: S.Schema<{ id: bigint; title: string; tags: string[]; rating: "G" | "PG" | "PG13" | "R"; }, Input>

type Film = S.Output<typeof filmSchema>;
// On hover: { id: bigint; title: string; tags: string[]; rating: "G" | "PG" | "PG13" | "R"; }

S.parseOrThrow(
  {
    id: 1n,
    title: "My first film",
    tags: ["Loved"],
    rating: "S",
  },
  filmSchema
);
// Throws S.Error with message: Failed at ["rating"]: Expected "G" | "PG" | "PG13" | "R", received "S"

// Or do it safely:
const result = S.safe(() => S.parseOrThrow(data, filmSchema));
if (result.error) {
  console.log(result.error.reason);
  // Expected "G" | "PG" | "PG13" | "R", received "S"
}

And yes, Sury also uses JIT under the hood, but with a focus on both creation and validation speed.

Final Thoughts

  • Zod v4 is a fantastic library, and Colin (the author) did an amazing job supporting both normal and JIT-optimized parsing.
  • If you care about performance—especially in dynamic or UI-heavy scenarios — benchmark your usage.
  • If you want the fastest schema validation, smallest bundle, and next-gen DX, give Sury a try. (And if you like it, a GitHub star would make my day! ⭐)

Thanks for reading! If you have questions, feedback, or want to see more benchmarks, let me know in the comments or ping me on X. See you soon with more updates! 🚀