Skip to main content

TS Ep 46: Inferring Tuples and Arrays

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
typescript - This article is part of a series.
Part 46: This Article
If you know how to destructure an array in JavaScript (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!

typescript - This article is part of a series.
Part 46: This Article