Variadic Tuples in TypeScript
Variadic tuples let you spread an array type into a tuple, creating flexible patterns like 'a string, then any number of booleans, then a number'. Learn the ...Type[] syntax and when to use it.
TypeScript variadic tuples are tuples that contain a spread element, a section that can hold any number of values of the same type. The syntax uses three dots followed by an array type inside a tuple: ...Type[]. It lets you say "this tuple starts with these fixed types, then has any number of those types, and ends with these fixed types."
This is the tuple equivalent of rest parameters in functions. Just as a function can collect any number of arguments with a rest parameter, a variadic tuple describes a structure with a variable-length section. The compiler enforces the fixed positions while allowing flexibility in the spread sections.
Basic variadic syntax
Place the spread syntax anywhere in a tuple to create a variable-length section. The spread absorbs zero or more elements of its declared type at that position.
type TaggedLog = [level: string, ...messages: string[]];
let infoLog: TaggedLog = ["info", "server started"];
let errorLog: TaggedLog = ["error", "connection failed", "retrying", "timeout"];The first element is always a string. After that, zero or more strings follow. Both examples above are valid because the spread section accepts any number of strings, including a single one.
When you destructure the tuple, the spread section collects into a regular array of the declared type:
function processLog([level, ...messages]: TaggedLog) {
console.log(`Level: ${level.toUpperCase()}`);
console.log(`Messages: ${messages.join(", ")}`);
}The level variable has type string because it comes from the fixed position. The messages variable has type string array because it collects everything after the first position. You can use any array method on messages without additional checks.
Spread positions: leading, middle, and trailing
You can place the variadic spread at the start, middle, or end of a tuple. Each position creates a different structural pattern with different practical uses.
type Trailing = [string, number, ...boolean[]];
type Leading = [...boolean[], string, number];
type Middle = [string, ...boolean[], number];Each variant has different behaviour at the type level. The fixed positions are always enforced at their exact locations, and the spread section absorbs any number of elements of its declared type.
The trailing spread is the most common pattern: a fixed start followed by a variable-length tail. This is useful for log entries, event data, or any format where a required prefix is followed by optional extra fields.
let a: Trailing = ["hello", 1];
let b: Trailing = ["hello", 1, true, false];The leading spread creates a variable-length prefix with a fixed suffix. This is less common but useful for data where the important fields come at the end.
let c: Leading = ["end", 42];
let d: Leading = [true, false, "end", 42];The middle spread creates a fixed start and end with a variable-length middle section. This pattern is useful for data with a known header and footer but an unknown body.
let e: Middle = ["start", 99];
let f: Middle = ["start", true, false, true, 99];How variadic tuples type rest parameters
TypeScript uses variadic tuples internally to type rest parameters. A function with a rest parameter is internally equivalent to a function whose parameter list is a variadic tuple.
function connect(host: string, port: number, ...flags: boolean[]) {
// ...
}This is equivalent to a single variadic tuple parameter that is destructured inside the function body. The tuple captures the exact shape of the parameter list: a string, then a number, then any number of booleans.
This means you can reuse tuple types as parameter lists. Define the shape once as a named tuple type and use it for both function signatures and data structures, so a change to the shape only needs to happen in one place.
Concatenating tuples
Variadic spreads also work with other tuple types, not just array types. You can spread one tuple into another to create concatenated types, building larger structures from smaller named pieces.
This is useful when several related tuple shapes share a common prefix, such as a set of API handlers that all start with the same context arguments before their own specific ones.
type Prefix = [string, number];
type Suffix = [boolean, string];
type Combined = [...Prefix, ...Suffix];The combined type flattens to a four-element tuple: string, number, boolean, string. This is useful for composing parameter lists or building larger tuple types from smaller, well-named components, especially when several functions share a common prefix or suffix shape.
Practical use: flexible event handlers
Variadic tuples let you type flexible callback signatures without overloads. The tuple parameter captures whatever arguments the handler expects, and the function passes them through with full type safety.
type EventHandler<T extends unknown[]> = (...args: T) => void;
function on<E extends unknown[]>(
event: string,
handler: EventHandler<E>,
...args: E
) {
handler(...args);
}This pattern means a click handler that expects two numbers is typed differently from a key handler that expects a string and a boolean. The generic parameter E captures the exact argument types, and the variadic tuple passes them through without losing any type information.
on("click", (x: number, y: number) => {
console.log(`Clicked at ${x}, ${y}`);
}, 100, 200);
on("keydown", (key: string, shift: boolean) => {
console.log(`Key: ${key}, Shift: ${shift}`);
}, "Enter", true);No overloads, no type assertions, and no loss of type safety. The variadic tuple carries type information from the callback signature through to the rest arguments.
This pattern preserves every type exactly as declared. It scales to any number of event types without writing additional function signatures or maintaining separate overload declarations as the API grows.
Variadic tuples and readonly
Add the readonly modifier to the tuple to prevent mutation while keeping the variadic spread. The readonly keyword goes before the entire tuple type and blocks all mutation methods.
This is useful for a tuple built once from configuration or parsed input, where the fixed prefix and the variable-length section should never change after creation.
type ReadonlyLog = readonly [level: string, ...messages: string[]];
let log: ReadonlyLog = ["info", "started"];
log[0] = "error";
log.push("extra");The index assignment and push call are both compiler errors because readonly prevents writes. Reading still works normally, so you can access elements by index, slice the array, and use map or filter without restriction.
The variadic spread section is just as protected as the fixed positions, since readonly applies to the whole tuple rather than to individual parts of it.
Common mistakes
The most common confusion is between variadic tuples and optional elements. An optional element makes one specific position optional, creating a fixed set of possible lengths, while a variadic spread makes an entire section accept any number of elements with no upper bound.
Choose optional for "maybe one more" and variadic for "any number more."
Another common mistake is expecting the spread section to remember an exact length. A spread of the form ...string[] creates a section with the type string array, not a section with a known count. The length of the entire tuple becomes number, not a literal.
If you need a known length, use a fixed tuple instead, as covered in tuples in TypeScript explained. For more on optional elements, see optional tuple elements in TypeScript.
Rune AI
Key Insights
- Variadic tuple syntax is ...Type[] inside a tuple type, representing zero or more elements of Type.
- You can place the spread at the start, middle, or end of a tuple.
- Variadic tuples are the foundation for TypeScript's own rest parameter typing.
- Combine variadic tuples with rest parameters to type functions with flexible argument lists.
- Variadic tuples are erased at runtime like all tuple types.
Frequently Asked Questions
What is a variadic tuple?
Can variadic spreads appear in the middle of a tuple?
How do variadic tuples differ from regular arrays?
Conclusion
Variadic tuples combine the precision of tuples with the flexibility of arrays. Use ...Type[] to represent variable-length sections within a tuple, whether at the start, middle, or end. The compiler enforces the fixed positions while allowing any number of elements in the spread sections, all at compile time with zero runtime cost.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.