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.

6 min read

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.

typescripttypescript
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:

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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

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.
RunePowered by Rune AI

Frequently Asked Questions

What is a variadic tuple?

A variadic tuple is a tuple type that uses ...Type[] spread syntax to represent a variable-length section of elements of the same type. For example, [string, ...number[]] means a string followed by any number of numbers.

Can variadic spreads appear in the middle of a tuple?

Yes. TypeScript supports leading spreads, middle spreads, and trailing spreads. For example, [string, ...boolean[], number] means a string, then any number of booleans, ending with a number.

How do variadic tuples differ from regular arrays?

Variadic tuples combine fixed positions with variable-length sections. A regular array like string[] has no fixed positions. A variadic tuple like [string, ...number[]] has exactly one string at the start, then an optional variable-length number section.

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.