SyntaxStudy
Sign Up
TypeScript Conditional Types and Infer
TypeScript Beginner 1 min read

Conditional Types and Infer

Conditional types, introduced in TypeScript 2.8, allow a type to vary based on a condition. The syntax T extends U ? X : Y means: if T is assignable to U, resolve to X, otherwise resolve to Y. This brings if-else logic to the type level and enables powerful type transformations that would be impossible with simple unions or intersections. The infer keyword appears only within the extends clause of a conditional type. It declares a type variable that TypeScript should infer from the actual type being matched against. This allows you to extract parts of a type — for example, the return type of a function, the element type of an array, or the resolve type of a Promise — in a generic way. Conditional types distribute over union types by default when the type being checked is a naked type parameter. This means the conditional is applied to each member of a union individually. Distributive conditional types are the mechanism behind many of TypeScript's built-in utility types like Exclude, Extract, and NonNullable.
Example
// Basic conditional type
type IsString<T> = T extends string ? true : false;
type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Extract return type (mirrors built-in ReturnType)
type MyReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;

function fetchUser(): Promise<{ id: number; name: string }> {
    return Promise.resolve({ id: 1, name: "Alice" });
}
type FetchResult = MyReturnType<typeof fetchUser>;
// Promise<{ id: number; name: string }>

// Unwrap a Promise
type Awaited2<T> = T extends Promise<infer U> ? Awaited2<U> : T;
type Resolved = Awaited2<Promise<Promise<number>>>; // number

// Element type of an array
type ElementType<T> = T extends (infer U)[] ? U : never;
type NumElem = ElementType<number[]>; // number

// Distributive conditional type
type NonNullable2<T> = T extends null | undefined ? never : T;
type Cleaned = NonNullable2<string | null | undefined | number>;
// string | number

// Exclude and Extract mirroring built-ins
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;

type Animals = "cat" | "dog" | "fish" | "bird";
type Pets    = MyExtract<Animals, "cat" | "dog">;  // "cat" | "dog"
type Wild    = MyExclude<Animals, "cat" | "dog">;  // "fish" | "bird"