bugl
bugl
HomeLearnPatternsPathsSearchPremium
HomeLearnPatternsPaths
Learn/TypeScript/TypeScript Core
TypeScript•TypeScript Core

TypeScript Conditional Types

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

This chapter

Overview
40

Lessons

241m

Read time

1. TypeScript Tutorial2. TypeScript Introduction3. TypeScript Getting Started4. TypeScript Simple Types5. TypeScript Explicit Types and Inference6. TypeScript Special Types7. TypeScript Arrays8. TypeScript Tuples9. TypeScript Object Types10. TypeScript Enums11. TypeScript Type Aliases and Interfaces12. TypeScript Union Types13. TypeScript Functions14. TypeScript Casting15. TypeScript Classes16. TypeScript Basic Generics17. TypeScript Utility Types18. TypeScript Keyof19. TypeScript Null & Undefined20. TypeScript Definitely Typed21. TypeScript 5.x Updates22. TypeScript Configuration23. TypeScript with Node.js24. TypeScript with React25. TypeScript Tooling26. TypeScript Advanced Types27. TypeScript Type Guards28. TypeScript Conditional Types29. TypeScript Mapped Types30. TypeScript Type Inference31. TypeScript Literal Types32. TypeScript Namespaces33. TypeScript Index Signatures34. TypeScript Declaration Merging35. TypeScript Async Programming36. TypeScript Decorators37. TypeScript in JavaScript Projects (JSDoc)38. TypeScript Migration Guide39. TypeScript Error Handling40. TypeScript Best Practices

On this page

Understanding Conditional Types in TypeScriptKey ConceptsCommon Use CasesBasic Conditional Type SyntaxDistributive Conditional TypesExtracting Types from Complex StructuresStandard Library UtilitiesRecursive Conditional TypesType-Level If-Else ChainsBest PracticesPerformance Considerations