Want a faster signal before interviews? Take the Interview Ready Check →

DAY 29 OF 30

Conditional types and infer

Yesterday, in one sentence

What is a mapped type in TypeScript?

Interview question

What are generics, and why are they useful?

A conditional type chooses a type based on a check, written like a ternary operator in JavaScript. It uses the extends keyword to check the type.

type Mutant = { power: string };

type IsMutant<T> = T extends Mutant ? true : false;

type Wolverine = IsMutant<{ power: "healing" }>; // true
type IronMan = IsMutant<{ name: "Tony" }>; // false

{ power: "healing" } has a power property, so it is assignable to Mutant, and Wolverine returns the true branch. { name: "Tony" } does not, so IronMan returns the false branch.

Watch out when the type is a unionUnion Type. The check runs once per member and the results are unioned together.

type Both = IsMutant<{ power: "flight" } | { name: "Tony" }>;
// true | false, which TypeScript collapses to boolean

infer declares a new type variable by inferring its type from the generic T being checked. It can only be used inside the extends clause of a conditional type.

type ElementOf<T> = T extends Array<infer U> ? U : never;

type A = ElementOf<string[]>; // string
type B = ElementOf<number[]>; // number

Array<infer U> reads as an array of something, and call that something U. When T is string[], U is inferred as string, and the true branch returns it. When T is not an array, there isn't anything to fill U, which is what the false branch is for.

The real use is deriving a type from a structure you did not write, so a change to it carries through automatically instead of leaving a hand written type behind.

// from a library, you did not write this
type ApiResponse = {
  data: { name: string; level: number }[];
  status: number;
};

type DataOf<T> = T extends { data: infer D } ? D : never;

type HeroList = DataOf<ApiResponse>;
// { name: string; level: number }[]

DataOf reads as give me the type of the data property on whatever object I pass in, or never if it does not have one. If the library changes the type behind data, HeroList changes with it.

Check yourself

What is Result here?
type Returns<T> = T extends () => infer R ? R : never

type Result = Returns<() => string[]>
What are conditional types in TypeScript, and what does infer do?