---
title: Avoid Dynamic Schema Creation in Hot Paths
impact: LOW-MEDIUM
impactDescription: Zod 4's JIT compilation makes schema creation slower; creating schemas in loops adds ~0.15ms per creation
tags: perf, dynamic, hot-path, optimization
---

## Avoid Dynamic Schema Creation in Hot Paths

Zod 4 uses JIT (Just-In-Time) compilation to speed up repeated parsing, but this makes initial schema creation slower. Avoid creating schemas inside loops or frequently-called functions—pre-create them instead.

**Incorrect (schema creation in hot path):**

```typescript
import { z } from "zod";

async function validateBatch(items: unknown[]) {
  const results = [];

  for (const item of items) {
    // Schema created for EACH item - slow!
    const schema = z.object({
      id: z.string(),
      value: z.number(),
    });

    results.push(schema.safeParse(item));
  }

  return results;
}

// 1000 items = 1000 schema creations = ~150ms overhead
```

**Correct (pre-created schema):**

```typescript
import { z } from "zod";

// Schema created ONCE
const itemSchema = z.object({
  id: z.string(),
  value: z.number(),
});

async function validateBatch(items: unknown[]) {
  // Reuse the same schema instance
  return items.map(item => itemSchema.safeParse(item));
}

// 1000 items = 1 schema creation + 1000 fast parses
```

**Dynamic schemas with caching:**

```typescript
import { z } from "zod";

// Cache for dynamically-configured schemas
const schemaCache = new WeakMap<object, z.ZodType>();

function getSchemaForConfig(config: { fields: string[] }) {
  // Check cache first
  if (schemaCache.has(config)) {
    return schemaCache.get(config)!;
  }

  // Create and cache
  const shape: Record<string, z.ZodString> = {};
  for (const field of config.fields) {
    shape[field] = z.string();
  }

  const schema = z.object(shape);
  schemaCache.set(config, schema);
  return schema;
}

// Subsequent calls with same config reuse cached schema
```

**Lazy schema creation:**

```typescript
import { z } from "zod";

// Schema created only when first used
let _userSchema: z.ZodObject<any> | null = null;

function getUserSchema() {
  if (!_userSchema) {
    _userSchema = z.object({
      id: z.string().uuid(),
      email: z.string().email(),
      profile: z.object({
        name: z.string(),
        avatar: z.string().url().optional(),
      }),
    });
  }
  return _userSchema;
}

// Or use a getter
const schemas = {
  _user: null as z.ZodType | null,
  get user() {
    if (!this._user) {
      this._user = z.object({
        /* ... */
      });
    }
    return this._user;
  },
};
```

**Benchmark considerations:**

```typescript
// Zod 4 JIT compilation:
// - Schema creation: ~0.15ms per schema
// - First parse: triggers JIT compile
// - Subsequent parses: 7-14x faster

// For schemas used once:
// - Creation + parse: ~0.15ms + first-parse overhead
// - Consider if validation is even needed

// For schemas used many times:
// - Create once, parse many: optimal
// - JIT compilation amortized over all parses
```

**When NOT to use this pattern:**

- One-off validation where schema is used once
- Dynamically generated forms where fields change per request
- Test files where performance doesn't matter

Reference: [Zod v4 Performance](https://zod.dev/v4#performance)
