Last Updated: 3/12/2026
Tuples
Unlike arrays, tuples are typically fixed-length arrays that specify different schemas for each index.
Basic Tuples
import * as z from "zod";
const MyTuple = z.tuple([
z.string(),
z.number(),
z.boolean()
]);
type MyTuple = z.infer<typeof MyTuple>;
// [string, number, boolean]
MyTuple.parse(["hello", 42, true]); // ✅
MyTuple.parse(["hello", 42]); // ❌ throws (too few elements)
MyTuple.parse(["hello", 42, true, "extra"]); // ❌ throws (too many elements)Variadic Tuples (Rest Arguments)
To add a variadic (“rest”) argument to a tuple:
const variadicTuple = z.tuple([z.string()], z.number());
// => [string, ...number[]]
variadicTuple.parse(["hello"]); // ✅
variadicTuple.parse(["hello", 1, 2, 3]); // ✅
variadicTuple.parse([42]); // ❌ throws (first element must be string)Type Inference
const PersonTuple = z.tuple([
z.string(), // name
z.number(), // age
z.boolean().optional() // isActive (optional)
]);
type PersonTuple = z.infer<typeof PersonTuple>;
// [string, number, boolean?]Tuples vs Arrays
Tuples
- Fixed length (or fixed + variadic)
- Different types for each position
- Position matters (first element is different from second)
const tuple = z.tuple([z.string(), z.number()]);
// [string, number] - exactly 2 elementsArrays
- Variable length
- Same type for all elements
- Position doesn’t matter
const array = z.array(z.string());
// string[] - any number of stringsCommon Use Cases
Function Return Values
// Simulating multiple return values
const getUserData = z.tuple([
z.string(), // username
z.number(), // id
z.date() // lastLogin
]);Coordinate Systems
const Point2D = z.tuple([z.number(), z.number()]);
// [x, y]
const Point3D = z.tuple([z.number(), z.number(), z.number()]);
// [x, y, z]CSV Row Parsing
const CsvRow = z.tuple([
z.string(), // name
z.string(), // email
z.coerce.number(), // age
z.enum(["active", "inactive"]) // status
]);Key-Value Pairs
const KeyValuePair = z.tuple([z.string(), z.unknown()]);
// [key, value]
const entries = z.array(KeyValuePair);
// Array of [string, unknown] pairsReact Hook Returns
// Similar to useState return type
const StateHook = z.tuple([
z.string(), // current value
z.function() // setter function
]);Advanced Patterns
Optional Elements
const OptionalTuple = z.tuple([
z.string(),
z.number().optional(),
z.boolean().optional()
]);
OptionalTuple.parse(["hello"]); // ✅
OptionalTuple.parse(["hello", 42]); // ✅
OptionalTuple.parse(["hello", 42, true]); // ✅Nested Tuples
const NestedTuple = z.tuple([
z.string(),
z.tuple([z.number(), z.number()]) // nested tuple
]);
type NestedTuple = z.infer<typeof NestedTuple>;
// [string, [number, number]]Combining with Rest Parameters
const MixedTuple = z.tuple(
[z.string(), z.number()], // required elements
z.boolean() // rest elements
);
// [string, number, ...boolean[]]
MixedTuple.parse(["hello", 42]); // ✅
MixedTuple.parse(["hello", 42, true, false, true]); // ✅Best Practices
- Use tuples when you know the exact structure and length
- Use arrays when all elements have the same type
- Add type aliases to make tuple schemas more readable
- Document positions with comments for clarity
- Consider objects if you need named fields instead of positional access