TypeScript Conditional Types

This chapter explores TypeScript conditional types, a powerful feature for performing type logic based on conditions. Conditional types enable developers to create dynamic and flexible types that adapt to varying inputs and use cases, providing enhanced type safety and maintainability.

Chapter Goal

  • To understand what conditional types are and their purpose in TypeScript.
  • To learn how to create and use conditional types effectively.
  • To explore practical applications of conditional types in real-world scenarios.

Key Characteristics for TypeScript Conditional Types

  • Type Logic: Performs checks and applies different types based on conditions.
  • Flexibility: Adapts dynamically to varying inputs.
  • Key Syntax: Uses T extends U ? X : Y to define conditions.
  • Combination with Utility Types: Works seamlessly with mapped types and utility types.
  • Dynamic Decision Making: Creates adaptive and reusable type definitions.

Basic Rules for TypeScript Conditional Types

  1. Use the extends keyword to define the condition.
  2. Return one type if the condition is true and another if false.
  3. Combine conditional types with utility types for advanced transformations.
  4. Test conditional types with diverse inputs to ensure correctness.
  5. Avoid overly complex conditional logic for readability.

Best Practices

  1. Use conditional types to handle varying type scenarios dynamically.
  2. Combine conditional types with mapped and utility types for enhanced functionality.
  3. Document complex conditional types for clarity and maintainability.
  4. Limit nested conditional logic to avoid reducing code readability.
  5. Test conditional types thoroughly with edge cases to ensure correctness.

Syntax Table

Serial No Component Syntax Example Description
1 Basic Conditional Type type IsString<T> = T extends string ? true : false; Checks if T is a string.
2 Nested Conditional Type type DeepCheck<T> = T extends Array<infer U> ? U extends string ? true : false : never; Checks nested conditions for arrays.
3 Default Type with Condition type WithDefault<T> = T extends undefined ? string : T; Assigns a default type when T is undefined.
4 Combining with Mapped Types `type Nullable = { [P in keyof T]: T[P] null };` Adds nullability to each property of T.
5 Infer Keyword Usage type ElementType<T> = T extends Array<infer U> ? U : never; Extracts element type from an array.

Syntax Explanation

1. Basic Conditional Type

What is a Basic Conditional Type

A type construct in TypeScript that dynamically evaluates a condition and determines the output type based on whether the condition evaluates to true or false. This mechanism allows for flexible and adaptive type definitions tailored to specific scenarios.

Syntax

type IsString<T> = T extends string ? true : false;

Detailed Explanation

  • The extends keyword checks if T extends string.
  • Returns true if T is a string; otherwise, returns false.

Example

type Result1 = IsString<string>; // true

type Result2 = IsString<number>; // false

Output

true

false

Notes

  • Use basic conditional types for simple type checks.

Warnings

  • Avoid overloading conditional types with unnecessary logic.

2. Nested Conditional Type

What is a Nested Conditional Type

A TypeScript construct that evaluates multiple conditions hierarchically, enabling developers to define types dynamically based on complex, nested criteria. This approach is especially useful for refining types in layered or deeply structured scenarios.

Syntax

type DeepCheck<T> = T extends Array<infer U> ? U extends string ? true : false : never;

Detailed Explanation

  • The T extends Array<infer U> checks if T is an array and extracts its element type (U).
  • The U extends string checks if the array elements are strings.

Example

type Check1 = DeepCheck<string[]>; // true

type Check2 = DeepCheck<number[]>; // false

Output

true

false

Notes

  • Use nested conditional types for complex type hierarchies.

Warnings

  • Keep nested logic readable and well-documented.

3. Default Type with Condition

What is a Default Type with Condition

A TypeScript construct that dynamically assigns a fallback type when a specified condition is satisfied, ensuring robust type handling in scenarios where a primary type might be undefined or missing.

Syntax

type WithDefault<T> = T extends undefined ? string : T;

Detailed Explanation

  • The T extends undefined checks if T is undefined.
  • Returns string if true; otherwise, returns T.

Example

type Result1 = WithDefault<undefined>; // string

type Result2 = WithDefault<number>; // number

Output

string

number

Notes

  • Use default types to handle missing or undefined values.

Warnings

  • Ensure default types align with the intended use case.

5. Infer Keyword Usage

What is the Infer Keyword

A specialized TypeScript construct that allows extracting and assigning a specific type from a conditional expression, enabling more dynamic and precise type inference in complex scenarios.

Syntax

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

Detailed Explanation

  • The T extends Array<infer U> checks if T is an array.
  • The infer U extracts the element type of the array.

Example

type Type1 = ElementType<string[]>; // string

type Type2 = ElementType<number[]>; // number

Output

string

number

Notes

  • Use infer to extract types dynamically from complex structures.

Warnings

  • Ensure extracted types align with the expected structure.

Real-Life Project

Project Name

Dynamic Data Validator

Project Goal

Demonstrates how to use conditional types to validate and process data dynamically based on its type.

Code for This Project

interface Validator<T> {

  validate: (input: T) => boolean;

}




type CreateValidator<T> = T extends string

  ? { type: 'string'; validate: (input: string) => boolean; }

  : T extends number

  ? { type: 'number'; validate: (input: number) => boolean; }

  : never;




const stringValidator: CreateValidator<string> = {

  type: 'string',

  validate: (input) => input.length > 0,

};




const numberValidator: CreateValidator<number> = {

  type: 'number',

  validate: (input) => input > 0,

};




console.log(stringValidator.validate('Hello')); // Output: true

console.log(numberValidator.validate(-5)); // Output: false

Save and Run

  1. Save the code in your development environment.
  2. Compile the TypeScript code using tsc.
  3. Run the resulting JavaScript file using node.

Expected Output

true

false

Insights

  • Conditional types enable dynamic and flexible type logic.
  • Combining conditional types with mapped types enhances their versatility.
  • Real-world examples like dynamic validators demonstrate their utility.

Key Takeaways

  • Conditional types provide powerful tools for dynamic type definitions.
  • Combine with utility and mapped types for advanced use cases.
  • Test conditional types thoroughly to ensure they handle diverse scenarios.