Tuple & Variadic Generics
🧬 Tuple & Variadic Generics
[...Acc, T] almost in passing. This chapter makes tuple spreading the main event: prepending and appending types, variadic generics that adapt to however many arguments a function actually receives, and the two functions every advanced-TypeScript tour eventually builds — a type-safe curry and a type-safe pipe.
Spreading Types Into Tuples
The ... spread syntax works inside a tuple type, not just inside array values — it splices one tuple's elements into another at the type level:
This is exactly the mechanism behind Chapter 2's Repeat<T, N, Acc> — every recursive call was really just Append in disguise.
🎛️ Variadic Tuple Types
A generic parameter constrained to unknown[] can represent any number of elements — variadic generics use this to describe functions whose argument count isn't fixed in advance:
Spread in the Return Type
[...T, ...U] in a function's return position joins two generic tuples exactly the way spreading joins them in a value.
Named Tuple Elements
[first: string, second: number] attaches labels for readability in tooltips — purely cosmetic, but makes long tuple types self-documenting.
Building a Type-Safe curry
Curry needs to peel one parameter off a function's parameter tuple at a time and recurse on what's left — the same tuple-recursion pattern from Chapter 2, applied to parameter lists instead of tree depth:
A Recursive, Fully-Typed Curry
Curried<Args, Return> recurses on the parameter tuple exactly like FlattenArray recursed on nested array types in Chapter 2 — one element consumed per recursive step, base case when the tuple is empty.
🔗 Type-Safe pipe
pipe needs the opposite shape: instead of one function's parameters, it chains many functions where each one's output must match the next one's input:
Overloads Without Writing Overloads
The pipe example above needed a separate overload signature for every chain length — exactly the pain variadic tuples exist to remove:
Manual Overloads vs One Variadic Signature
Manual Overloads
One Variadic Signature
This is the same trade-off libraries like Promise.all and Function.prototype.bind's type definitions navigate internally — a handful of hand-written overloads for the common, small cases, falling back to a looser variadic signature for everything else.
💻 Coding Challenges
Challenge 1: Write Head and Tail
Write two tuple utility types: Head<T> (the first element's type) and Tail<T> (a tuple of every element except the first), each using tuple destructuring in the type position.
Goal: Practice the [infer First, ...infer Rest] pattern used inside Curried in this chapter.
Challenge 2: Write a Type-Safe zip
Write a zip<T extends unknown[], U extends unknown[]>(a: T, b: U) function whose return type pairs up each element by position — e.g. zip([1, 2], ["a", "b"]) should type as [number, string][].
Goal: Practice combining two generic tuples element-by-element rather than just concatenating them.
Challenge 3: Extend Curried for Multi-Arg Currying
The curry example only ever peels off one argument at a time. Extend the type so calling with multiple arguments at once (e.g. curriedAdd3(1, 2)(3)) is also valid and correctly typed.
Goal: Practice recursing on a tuple by removing a variable-length prefix rather than always exactly one element.
const arr = [1, "a"] infers as (string | number)[] — a flexible array, not the tuple [number, string] you probably wanted for something like the concat example. Add as const ([1, "a"] as const) to force a literal, fixed-length tuple type, or annotate the parameter explicitly with a tuple type as the chapter's examples do. Forgetting this is the single most common reason a "type-safe" tuple function silently falls back to loose array typing.
🎯 What's Next
With tuples fully under control, the next chapter turns to a different kind of reusability: Type Predicates & Type Guards at Scale — building narrowing utilities that stay useful across an entire codebase instead of being written fresh for every discriminated union.