bugl
bugl
HomeLearnPatternsPathsSearch
HomeLearnPatternsPathsSearch

Loading lesson path

Learn/TypeScript/TypeScript Core
TypeScript•TypeScript Core

TypeScript Conditional Types

Flash cards

Review the key moves

1/4
Core idea

What is the main idea behind TypeScript Conditional Types?

Lesson checks

Practice each idea before moving on

Short Mimo-style checks built from this lesson's code, terms, and sequence.

1Quick choice

Which statement best captures the main point of this lesson?

2Fill blank

Complete the missing token from the example code.

___ IsString<T> = T extends string ? true : false;
3Order

Put the learning moves in the order that makes the concept easiest to apply.

Distributive Conditional Types
Basic Conditional Type Syntax
Understanding Conditional Types in TypeScript

Understanding Conditional Types in TypeScript

Conditional types in TypeScript enable you to create types that depend on other types, similar to how if-else statements work in JavaScript.

They're a powerful feature that allows for sophisticated type transformations and type-level programming.

Key Concepts

  • Type-level logic : Perform conditional checks on types
  • Type inference : Extract and manipulate types using infer
  • Composition : Combine with other TypeScript features
  • Utility types : Build powerful type utilities

Common Use Cases

  • Type-safe function overloading
  • API response type transformations
  • Complex type validations
  • Building reusable type utilities
  • Advanced type inference

Basic Conditional Type Syntax

Conditional types use the form T extends U ? X : Y , which means:

"if type T extends (or is assignable to) type U , use type X , otherwise use type Y ".

Example

type IsString<T> = T extends string ? true : false;
// Usage examples type Result1 = IsString<string>;  // true type Result2 = IsString<number>;  // false type Result3 = IsString<"hello">; // true (literal types extend their base types) // We can use this with variables too
let a: IsString<string>; // a has type 'true'
let b: IsString<number>; // b has type 'false'

Distributive Conditional Types

Conditional types are particularly useful with union types, where they're automatically distributed over union members:

Example

type ToArray<T> = T extends any ? T[] : never;
// When used with a union type, it applies to each member of the union type StringOrNumberArray = ToArray<string | number>; // This becomes ToArray<string> | ToArray<number> // Which becomes string[] | number[] // We can also extract specific types from a union type ExtractString<T> = T extends string ? T : never; type StringsOnly = ExtractString<string | number | boolean | "hello">; // Result: string | "hello"

Extracting Types from Complex Structures

The infer keyword allows you to declare a type variable within the condition part of a conditional type and then use it in the true branch of the condition:

Example

// Extract the return type of a function type type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; // Examples function greet() { return "Hello, world!"; } function getNumber() { return 42; } type GreetReturnType = ReturnType<typeof greet>;   // string type NumberReturnType = ReturnType<typeof getNumber>; // number // Extract element type from array type ElementType<T> = T extends (infer U)[] ? U : never; type NumberArrayElement = ElementType<number[]>; // number type StringArrayElement = ElementType<string[]>; // string

Standard Library Utilities

TypeScript includes several built-in conditional types in its standard library:

Example

// Extract<T, U> - Extracts types from T that are assignable to U type OnlyStrings = Extract<string | number | boolean, string>; // string // Exclude<T, U> - Excludes types from T that are assignable to U type NoStrings = Exclude<string | number | boolean, string>; // number | boolean // NonNullable<T> - Removes null and undefined from T type NotNull = NonNullable<string | null | undefined>; // string // Parameters<T> - Extracts parameter types from a function type type Params = Parameters<(a: string, b: number) => void>; // [string, number] // ReturnType<T> - Extracts the return type from a function type type Return = ReturnType<() => string>; // string

Recursive Conditional Types

Conditional types can be used recursively to create complex type transformations:

Example

// Deeply unwrap Promise types type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T; // Examples type A = UnwrapPromise<Promise<string>>;           // string type B = UnwrapPromise<Promise<Promise<number>>>;   // number type C = UnwrapPromise<boolean>;                   // boolean

Type-Level If-Else Chains

Chain multiple conditions together for complex type logic:

Example

type TypeName<T> = T extends string  ? "string" : T extends number  ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object";
// Usage type T0 = TypeName<string>;      // "string" type T1 = TypeName<42>;         // "number" type T2 = TypeName<true>;       // "boolean" type T3 = TypeName<() => void>; // "function" type T4 = TypeName<Date[]>;     // "object"

Conditional types are powerful when creating generic utilities and type-safe libraries:

Example

// A function that returns different types based on input type function processValue<T>(value: T): T extends string ? string : T extends number ? number : T extends boolean ? boolean : never {
if (typeof value === "string") {
  return value.toUpperCase() as any; // Type assertion needed due to limitations
} else if (typeof value === "number") {
return (value * 2) as any;
} else if (typeof value === "boolean") {
return (!value) as any;
} else {
throw new Error("Unsupported type");
}
}
// Usage
const stringResult = processValue("hello"); // Returns "HELLO" (type is string)
const numberResult = processValue(10);      // Returns 20 (type is number)
const boolResult = processValue(true);      // Returns false (type is boolean)

Best Practices

Do

  • Use conditional types for complex type transformations
  • Combine with infer for type extraction
  • Create reusable type utilities
  • Document complex conditional types
  • Test edge cases in your type definitions

Don't

  • Overuse complex conditional types when simple types would suffice
  • Create deeply nested conditional types that are hard to understand
  • Forget about performance implications with very complex types
  • Use conditional types for runtime logic

Performance Considerations

  • Deeply nested conditional types can increase compile times
  • Consider using type aliases for intermediate results
  • Be mindful of TypeScript's recursion depth limits

Previous

TypeScript Type Guards

Next

TypeScript Mapped Types