Skip to main content

Conditional Types

Conditional types choose between two output types based on a type-level check: CheckType extends ExtendsType ? TrueType : FalseType.

1type IsString<T> = T extends string ? 'yes' : 'no';2
3type A = IsString<string>; // 'yes'4type B = IsString<number>; // 'no'
Try
TypeScript comparison

Conditional types match TypeScript in shape and most behaviors: distributivity over unions, infer on the right-hand side of extends (including the infer T extends Bound constraint form), and the [T] extends [...] non-distributive opt-out all work the same way.

When to use this

Use conditional types when a type needs to depend on another type — for example, extracting parts of a generic type or expressing return types that vary with input. For simple cases where you know the concrete types upfront, a plain union is usually clearer. For functions with multiple signatures, consider overloads instead.

Basic Usage

If CheckType is a subtype of ExtendsType, the conditional type will be evaluated to TrueType. Otherwise, it will be evaluated to FalseType.

The following example illustrates both cases.

1class Animal {}2class Dog extends Animal {}3
4type TypeofAnimal = Dog extends Animal ? 'animal' : 'unknown'; // evaluates to 'animal'5type TypeofString = string extends Animal ? 'animal' : 'unknown'; // evaluates to 'unknown'
Try

Generic Conditional Types

This might not look very useful, since you already know what type it will evaluate to. However, combining with generics, you can perform complex computations over types. For example, you can write down a type-level typeof operator:

1type TypeOf<T> =2  T extends null ? 'null' :3  T extends void ? 'undefined' :4  T extends string ? 'string' :5  T extends number ? 'number' :6  T extends boolean ? 'boolean' :7  T extends (...ReadonlyArray<empty>)=>unknown ? 'function' : 'object'8
9type T1 = TypeOf<null>; // evaluates to 'null'10type T2 = TypeOf<void>; // evaluates to 'undefined'11type T3 = TypeOf<string>; // evaluates to 'string'12type T4 = TypeOf<number>; // evaluates to 'number'13type T5 = TypeOf<boolean>; // evaluates to 'boolean'14type T6 = TypeOf<(string)=>boolean>; // evaluates to 'function'15type T7 = TypeOf<{foo: string}>; // evaluates to 'object'
Try

Function return types dependent on input types

Conditional types also allow you to intuitively describe the conditions for choosing different function overloads:

1declare function wrap<T>(value: T): T extends string ? { type: 'string', value: string }2                                  : T extends number ? { type: 'number', value: number }3                                  : { type: 'unsupported' }4
5const v1 = wrap(3);   // has type { type: 'number', value: number }6const v2 = wrap('4'); // has type { type: 'string', value: string }7const v3 = wrap({});  // has type { type: 'unsupported' }
Try

The above example can also be written with function overload:

1declare function wrap(value: string): { type: 'string', value: string }2declare function wrap(value: number): { type: 'number', value: number }3declare function wrap(value: unknown): { type: 'unsupported' }4
5const v1 = wrap(3);   // has type { type: 'number', value: number }6const v2 = wrap('4'); // has type { type: 'string', value: string }7const v3 = wrap({});  // has type { type: 'unsupported' }
Try

Inferring Within Conditional Types

You can use the power of conditional types to extract parts of a type using infer types. For example, the builtin ReturnType is powered by conditional types:

1type ReturnType<T> = T extends (...args: ReadonlyArray<empty>) => infer Return ? Return : empty;2
3type N = ReturnType<(string) => number>; // evaluates to `number`4type S = ReturnType<(number) => string>; // evaluates to `string`
Try

We used the infer type here to introduce a new generic type variable named Return, which can be used in the type branch of the conditional type. Infer types can only appear on the right hand side of the extends clause in conditional types. Flow will perform a subtyping check between the check type and the extends type to automatically figure out its type based on the input type T.

In the example of type N = ReturnType<(string) => number>, Flow checks if (string) => number is a subtype of (...args: ReadonlyArray<empty>) => infer Return, and during this process Return is constrained to number.

When doing extractions like the above example, you usually want the conditional type to always choose the true branch where the type is successfully extracted. For example, silently choosing the false branch is not great:

1type ExtractReturnTypeNoValidation<T> =2  T extends (...args: ReadonlyArray<empty>) => infer Return ? Return : any;3
41 as ExtractReturnTypeNoValidation<string>; // no error :(
Try

Instead, you might want Flow to error when the input is not a function type. This can be accomplished by adding constraints to the type parameter:

1type ReturnType<T extends (...args: ReadonlyArray<empty>) => unknown> =2  T extends (...args: ReadonlyArray<empty>) => infer Return ? Return : any;3
41 as ReturnType<(string) => number>;51 as ReturnType<string>; // Errorincompatible-typeCannot instantiate ReturnType because in type argument T: string [1] is incompatible with (...args: ReadonlyArray<empty>) => unknown [2].
Try

Constraining Inferred Types with extends

An infer variable can carry its own constraint, written infer T extends Bound. The bound does two things.

It acts as a filter on the candidate type. If the type matched against infer X does not satisfy the bound, the extends check fails and the conditional type evaluates to its false branch:

1type ElementIfNumeric<T> = T extends Array<infer X extends number> ? X : false;2
3type A = ElementIfNumeric<Array<number>>; // evaluates to number4type B = ElementIfNumeric<Array<string>>; // string violates the bound, so evaluates to false51 as A; // OK61 as B; // ERROR: number is not falseincompatible-typeCannot cast 1 to B because 1 [1] is incompatible with false [2].
Try

It also supplies a default. When an infer variable is underconstrained, because the matched type never determines it, the variable falls back to its bound rather than to mixed:

1// Without a bound, an underconstrained infer defaults to mixed:2type Unbounded<T> = T extends null | [infer X] ? X : number;3type R1 = Unbounded<null>; // X is never matched, so evaluates to mixed41 as R1; // OK5
6// With a bound, it defaults to that bound instead:7type Bounded<T> = T extends null | [infer X extends string] ? X : number;8type R2 = Bounded<null>; // X is never matched, so evaluates to string9'' as R2; // OK101 as R2; // ERROR: number is not stringincompatible-typeCannot cast 1 to R2 because 1 [1] is incompatible with string [2].
Try

Distributive Conditional Types

When a generic conditional type is given a union type as a type argument, the conditional distributes over the union's members. For example, the TypeOf example above can distribute over a union:

1type TypeOf<T> =2  T extends null ? 'null' :3  T extends void ? 'undefined' :4  T extends string ? 'string' : 'other';5
6type StringOrNull = TypeOf<string | null>; // evaluates to 'string' | 'null'
Try

This works by first breaking up the union type, and then passing each type to the conditional type to be evaluated separately. In the example above, this looks something like:

TypeOf<string | null>
--> (break up the union) --> TypeOf<string> | TypeOf<null>
--> (evaluate each conditional type separately) --> 'string' | 'null'

If you want to avoid this behavior, you can wrap the check type and extends type with unary tuple type:

1type NonDistributiveTypeOf<T> =2  [T] extends [null] ? 'null' :3  [T] extends [void] ? 'undefined' :4  [T] extends [string] ? 'string' : 'other';5
6type Other = NonDistributiveTypeOf<string | null>; // evaluates to 'other'
Try

This trick works because Flow will only enable the distributive behavior of conditional type if the check type is a generic type. The example above does not choose any true branch of the conditional type, because [string | null] is not a subtype of [null], [void], or [string], since tuples are invariantly typed.

Adoption

To use conditional types, you need to upgrade your infrastructure so that it supports the syntax:

  • prettier: version 3 or later, with the @prettier/plugin-hermes plugin installed (see these instructions).
  • babel with babel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.
  • eslint with hermes-eslint. See our ESLint guide for setup instructions.

See Also

  • Mapped Types — another advanced type for transforming object types
  • Indexed Access Types — extracting property types, often combined with conditional types
  • Generics — conditional types are most useful with generic type parameters
  • Utility Types — built-in type transformations like Partial, Readonly, and Pick