--- title: Avoid Double Validation impact: HIGH impactDescription: Parsing the same data twice wastes CPU cycles; in hot paths this adds measurable latency tags: parse, performance, optimization, architecture --- ## Avoid Double Validation Once data is validated by Zod, trust the result. Re-validating the same data in multiple layers doubles CPU usage and adds latency. Pass the typed result through your application instead. **Incorrect (validating at every layer):** ```typescript import { z } from 'zod' const userSchema = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string(), }) // Controller validates export async function POST(req: NextRequest) { const body = await req.json() const user = userSchema.parse(body) // First parse return await userService.create(user) } // Service validates again const userService = { async create(data: unknown) { const user = userSchema.parse(data) // Second parse - redundant return await userRepository.insert(user) } } // Repository validates again const userRepository = { async insert(data: unknown) { const user = userSchema.parse(data) // Third parse - wasteful return await db.users.create({ data: user }) } } ``` **Correct (validate once, pass typed data):** ```typescript import { z } from 'zod' const userSchema = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string(), }) type User = z.infer // Controller validates at boundary export async function POST(req: NextRequest) { const body = await req.json() const result = userSchema.safeParse(body) if (!result.success) { return NextResponse.json({ errors: result.error.issues }, { status: 400 }) } // Pass validated, typed data return await userService.create(result.data) } // Service receives typed data, no re-validation needed const userService = { async create(user: User) { // user is guaranteed to match schema return await userRepository.insert(user) } } // Repository receives typed data const userRepository = { async insert(user: User) { return await db.users.create({ data: user }) } } ``` **When you might validate at multiple layers:** ```typescript // Different schemas for different layers const apiUserSchema = z.object({ email: z.string().email(), password: z.string().min(8), // Only in API layer }) const dbUserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), passwordHash: z.string(), // Transformed before storage }) // API validates input format export async function POST(req: NextRequest) { const input = apiUserSchema.parse(await req.json()) const user = await userService.create(input) return NextResponse.json(user) } // Service transforms and validates for storage const userService = { async create(input: z.infer) { const dbUser = dbUserSchema.parse({ id: crypto.randomUUID(), email: input.email, passwordHash: await hash(input.password), }) return await userRepository.insert(dbUser) } } ``` **When NOT to use this pattern:** - When schemas differ between layers (API vs DB shape) - When data crosses trust boundaries (external service response) - During development when debugging data flow Reference: [Zod Performance](https://zod.dev/v4#performance)