const [first, ...rest] = arr), you already know how to destructure a Tuple in TypeScript. The type system perfectly mirrors runtime array destructuring by combining infer with the spread syntax (...).1. Extracting the Array Element Type (Flatten<T>)#
The most fundamental use case for infer with arrays is extracting the type of the elements inside it.
If you have a type that might be an array, or might be a raw value, you can write a Flatten utility that unwraps the array structure.
// Pattern Match: Is T an array of SOME type U?
// If YES: Return U.
// If NO: Return T (it's not an array).
type Flatten<T> = T extends (infer U)[] ? U : T;
// 🟢 Unwraps the array
type Str = Flatten<string[]>;
// Inferred Type: string
type Num = Flatten<number[]>;
// Inferred Type: number
// 🟢 Safely returns the raw type if it's not an array
type Obj = Flatten<{ id: string }>;
// Inferred Type: { id: string }
2. Deconstructing Tuples (Head and Tail)#
Because tuples have fixed lengths and strongly typed indices, we can use the rest/spread operator (...) inside our conditional type pattern to slice the tuple apart.
Extracting the First Element (Head<T>)#
In functional programming, the first element of a list is called the “Head”. We can extract it by placing infer First at the 0 index, and spreading any[] for the rest of the tuple.
// 1. Constrain T to be an array of any type.
// 2. Pattern Match: Does T look like [FirstElement, ...everythingElse]?
type Head<T extends any[]> = T extends [infer First, ...any[]] ? First : never;
type T1 = Head<[string, number, boolean]>;
// Inferred Type: string
type T2 = Head<[Date, string]>;
// Inferred Type: Date
// If the tuple is empty, the pattern fails to match!
type T3 = Head<[]>;
// Inferred Type: never
Extracting the Remaining Elements (Tail<T>)#
The “Tail” represents everything except the first element. To extract the tail, we ignore the first element with any, and apply the infer keyword to the spread operator!
// Pattern Match: Does T look like [IgnoreFirst, ...RestOfElements]?
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
type RestOfT1 = Tail<[string, number, boolean]>;
// Inferred Type: [number, boolean]
type RestOfT2 = Tail<[Date, string]>;
// Inferred Type: [string]
3. Extracting the Last Element (Last<T>)#
TypeScript’s tuple destructuring is incredibly advanced. Unlike early versions of JavaScript, TypeScript allows you to place the spread operator at the beginning or middle of a tuple pattern!
This means extracting the absolute last element of a variadic tuple is trivial:
// Pattern Match: Does T look like [...EverythingBefore, LastElement]?
type Last<T extends any[]> = T extends [...any[], infer LastElement] ? LastElement : never;
type FinalType1 = Last<[string, number, boolean]>;
// Inferred Type: boolean
type FinalType2 = Last<[Date, string, Error]>;
// Inferred Type: Error
4. Real-World Architecture: Strongly Typed Middleware#
Why does this matter? Tuple destructuring is the engine that powers highly complex variadic functions, like functional compose / pipe utilities, or middleware chains in frameworks like Redux and Express.
Imagine writing a function that accepts a tuple of middleware functions. You can use tuple extraction to guarantee that the output of Middleware A perfectly matches the required input of Middleware B!
Summary & Next Steps#
In this episode:
- We unpacked basic arrays using
T extends (infer U)[]. - We extracted the first element of a tuple (
Head<T>). - We extracted the remaining elements into a new tuple using spread inference (
...infer Rest). - We extracted the final element of a tuple by prefixing the spread (
[...any[], infer Last]).
In Episode 47: Template Literal Types, we will combine everything we’ve learned so far to dynamically generate hundreds of exact string types (like CSS classes or event names) using template string permutations!

