JS Learning

Conditional Types

Advanced TypeScriptSection 3 of 4advanced

Topic Overview

Take your time to understand each concept. Practice with the examples below!

Conditional types are like if-else statements for types! They let you create types that change based on conditions, making your type system incredibly flexible and powerful.

What are Conditional Types? Conditional types use the same ternary operator (? :) as JavaScript, but for types instead of values. They follow the pattern: T extends U ? X : Y

How They Work:

1. Check if type T matches condition U 2. If yes, the type becomes X 3. If no, the type becomes Y 4. Just like if-else, but at the type level!

Basic Examples:

  • Is it a string? T extends string ? true :false
  • Nullable check:T extends null | undefined ? never : T
  • Array check:T extends any[] ? T[number] : T

The Power of 'infer':

The infer keyword lets you extract types from other types:

  • Get return type:T extends (...args: any[]) => infer R ? R : never
  • Get array element:T extends (infer U)[] ? U : T
  • Extract promise type:T extends Promise<infer U> ? U : T

Real-World Use Cases:

  • API response handling:Different types based on status
  • Form validation:Type based on field type
  • Component props:Conditional required fields
  • State machines:Type based on current state

Distributive Conditional Types:

When used with unions, conditional types distribute:

  • Input:string | number
  • Condition:T extends string ? 'text' : 'other'
  • Result:'text' | 'other'

Advanced Patterns:

  • Exclude null:NonNullable<T>
  • Extract keys:Extract<T, U>
  • Filter types:Exclude<T, U>
  • Type guards:T extends U ? T : never

Why Use Conditional Types?

  • Create smart types that adapt
  • Build powerful utility types
  • Type-safe conditional logic
  • Reduce code duplication
  • Express complex type relationships

Example

// Basic conditional type
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// More practical example
type NonNullable<T> = T extends null | undefined ? never : T;
type SafeString = NonNullable<string | null>; // string
type SafeNumber = NonNullable<number | undefined>; // number
// Conditional type with infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getString(): string { return "hello"; }
function getNumber(): number { return 42; }
type StringReturn = ReturnType<typeof getString>; // string
type NumberReturn = ReturnType<typeof getNumber>; // number
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]
// Complex conditional type example
type ApiResponse<T> = T extends string
? { message: T }
: T extends number
? { code: T }
: T extends boolean
? { success: T }
: { data: T };
type MessageResponse = ApiResponse<string>; // { message: string }
type CodeResponse = ApiResponse<number>; // { code: number }
type SuccessResponse = ApiResponse<boolean>; // { success: boolean }
type DataResponse = ApiResponse<object>; // { data: object }
console.log("Conditional types provide powerful type transformations!");