profileRyan KesPGP keyI build stuffEmailGithubTwitterLast.fmMastodonMatrix

TypeScript Recursive Conditional Types

Description

In JavaScript it's common to see functions that can flatten and build up container types at arbitrary levels. Examples of these are:

  • The .then() method on instances of promise. .then(...) unwraps each promise until it finds a value that's not "fromise-like" and passes that value to a callback.
  • The Array flat & flatMap prototypes.

Since TypeScript 4.1, TypeScript eases some restrictions on conditional types, so that they can model those patterns. Conditional types can immediately reference themselves within their branches, making it easier to write recursive type aliases.

Examples

Get the element types of nested arrays

type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T

function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[] {
  throw "not implemented"
}

// All of these return the type 'number[]':
deepFlatten([1, 2, 3])
deepFlatten([[1], [2, 3]])
deepFlatten([[1], [[2]], [[[3]]]])

Awaited type to deeply unwrap Promises

type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T

/// Like `promise.then(...)`, but more accurate in types.
declare function customThen<T, U>(
  p: Promise<T>,
  onFulfilled: (value: Awaited<T>) => U
): Promise<Awaited<U>>

Caveats

Keep in mind that while these recursive types are powerful, but they should be used responsibly and sparingly.

First off, these types can do a lot of work which means that they can increase type-checking time. Trying to model numbers in the Collatz conjecture or Fibonacci sequence might be fun, but don’t ship that in .d.ts files on npm.

Apart from being computationally intensive, these types can hit an internal recursion depth limit on sufficiently-complex inputs. When that recursion limit is hit, that results in a compile-time error. In general, it’s better not to use these types at all than to write something that fails on more realistic examples.

Related

Footnotes