Conditional types select one of two types based on a condition.

Basic Syntax

T extends U ? X : Y

If T extends U, the type is X, otherwise Y.

Simple Conditional Types

type IsArray<T> = T extends Array<any> ? true : false;

type Test1 = IsArray<string[]>;  // true
type Test2 = IsArray<string>;    // false

Type Inference in Conditional Types

Use infer to extract types.

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

type Str = Flatten<string[]>;  // string
type Num = Flatten<number>;    // number

Multiple infer

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Func = () => string;
type Ret = ReturnType<Func>; // string

Distributive Conditional Types

Conditional types distribute over union types.

type ToArray<T> = T extends any ? T[] : never;

type StrArrOrNumArr = ToArray<string | number>;
// string[] | number[]

// To prevent distribution, use brackets
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type StrOrNumArr = ToArrayNonDist<string | number>;
// (string | number)[]

Non-Nullable Type

type NonNullable<T> = T extends null | undefined ? never : T;

type T0 = NonNullable<string | number | undefined>;
// string | number

Extract and Exclude

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

type T0 = Exclude<"a" | "b" | "c", "a">;
// "b" | "c"

type T1 = Extract<"a" | "b" | "c", "a" | "f">;
// "a"

Function Parameter Types

type Parameters<T extends (...args: any) => any> = 
  T extends (...args: infer P) => any ? P : never;

type Func = (a: string, b: number) => void;
type Params = Parameters<Func>;
// [string, number]

Return Type

type ReturnType<T extends (...args: any) => any> = 
  T extends (...args: any) => infer R ? R : any;

type Func = () => string;
type Ret = ReturnType<Func>;
// string

Exercises

Exercise 1: Basic Conditional

// Create a conditional type 'IsString' that:
// - Returns true if T is string
// - Returns false otherwise

Exercise 2: Type Inference

// Create a conditional type 'ElementType' that:
// - Extracts the element type from an array
// - Returns T if T is not an array

Exercise 3: Distributive Types

// Create a conditional type that:
// - Distributes over a union
// - Transforms each member
// - Test with string | number

Exercise 4: Function Types

// Create a conditional type 'FirstParameter' that:
// - Extracts the first parameter type from a function
// - Returns never if not a function

Exercise 5: Complex Conditional

// Create a conditional type that:
// - Checks if T is a Promise
// - If yes, extracts the resolved type
// - If no, returns T

Best Practices

  1. Understand distribution - Conditional types distribute over unions
  2. Use infer for extraction - Extract types from complex types
  3. Prevent distribution when needed - Use brackets
  4. Combine with mapped types - Powerful type transformations
  5. Test your types - Ensure they work as expected

Common Mistakes

  1. Not understanding distribution
// This distributes
type T = ToArray<string | number>; // string[] | number[]

// This doesn't
type T = ToArrayNonDist<string | number>; // (string | number)[]
  1. Wrong infer placement
// ❌ Wrong
type Bad<T> = T extends Array<any> ? infer U : T;

// ✅ Correct
type Good<T> = T extends Array<infer U> ? U : T;
  1. Forgetting extends constraint
// ❌ Might not work as expected
type Bad<T> = T extends any ? T : never;

// ✅ Better with constraint
type Good<T extends any> = T extends any ? T : never;

Next Steps

Learn about Error Handling.