Understanding TypeScript Conditional Types: A Beginner’s Guide

TypeScript conditional types are one of the most powerful features for creating flexible and reusable type definitions. Whether you’re new to TypeScript or looking to level up your type system knowledge, this guide will help you master conditional types from the ground up.

Conditional types allow you to create types that depend on other types, similar to how if statements work in regular JavaScript. This powerful feature enables you to write more dynamic and reusable type definitions.

Table of Contents

What Are Conditional Types?

Conditional types take the form T extends U ? X : Y, where:

  • T is the type we’re checking
  • U is the condition type
  • X is the type if condition is true
  • Y is the type if condition is false

Let’s look at a simple example:

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

// Usage
type Result1 = IsString<'hello'>; // true
type Result2 = IsString<123>; // false
Code language: JavaScript (javascript)

Basic Conditional Type Patterns

Let’s explore some common patterns you’ll encounter when working with conditional types:

1. Type Checking

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

type StringArray = IsArray<string[]>; // true
type StringType = IsArray<string>; // false
Code language: JavaScript (javascript)

2. Type Extraction

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

type ElementType = UnwrapArray<number[]>; // number
type NonArrayType = UnwrapArray<string>; // string
Code language: HTML, XML (xml)

Practical Use Cases

1. Function Return Type Inference

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

function greet() { return 'Hello'; }
function add(a: number, b: number) { return a + b; }

type GreetReturn = GetReturnType<typeof greet>; // string
type AddReturn = GetReturnType<typeof add>; // number
Code language: JavaScript (javascript)

2. Nullable Type Handling

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

type Example = NonNullable<string | null | undefined>; // string
Code language: JavaScript (javascript)

Advanced Patterns

1. Distributive Conditional Types

Conditional types are distributive when used with union types:

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

type StringOrNumber = string | number;
type ArraysType = ToArray<StringOrNumber>; // string[] | number[]
Code language: HTML, XML (xml)

2. Type Inference in Conditional Types

type FirstArgument<T> = T extends (first: infer U, ...args: any[]) => any ? U : never;

function example(name: string, age: number) {}

type FirstArg = FirstArgument<typeof example>; // string
Code language: JavaScript (javascript)

Best Practices

  1. Keep It Simple: Start with basic conditional types and gradually move to more complex patterns as needed.
// Good
type IsNumber<T> = T extends number ? true : false;

// Too Complex
type ComplexType<T> = T extends Array<infer U> 
  ? U extends object 
    ? U extends Function 
      ? ReturnType<U> 
      : never 
    : never 
  : never;
Code language: PHP (php)
  1. Use Meaningful Type Names: Choose descriptive names that reflect the purpose of your conditional types.
// Good
type ExtractPromiseValue<T> = T extends Promise<infer U> ? U : T;

// Bad
type E<T> = T extends Promise<infer U> ? U : T;
Code language: HTML, XML (xml)
  1. Document Complex Types: Add comments to explain the purpose and behavior of complex conditional types.
// Extracts the type of array elements, returns 'never' for non-array types
type ElementOf<T> = T extends Array<infer E> ? E : never;
Code language: JavaScript (javascript)

Common Gotchas and Solutions

1. Handling Never Type

type HandleNever<T> = [T] extends [never] ? true : false;

type Test1 = HandleNever<never>; // true
type Test2 = HandleNever<string>; // false
Code language: JavaScript (javascript)

2. Working with Union Types

type NonDistributive<T> = [T] extends [string | number] ? true : false;

type Test3 = NonDistributive<string | number>; // true
type Test4 = NonDistributive<boolean>; // false
Code language: JavaScript (javascript)

Practical Examples

Building a Type-Safe Event System

type EventMap = {
  click: { x: number; y: number };
  change: { value: string };
};

type EventType<T extends keyof EventMap> = EventMap[T];

function handleEvent<T extends keyof EventMap>(event: T, data: EventType<T>) {
  // Type-safe event handling
  console.log(event, data);
}

// Usage
handleEvent('click', { x: 100, y: 200 }); // Valid
handleEvent('change', { value: 'new value' }); // Valid
// handleEvent('click', { value: 'error' }); // Type error!
Code language: JavaScript (javascript)

Conditional types are a cornerstone of TypeScript’s type system, enabling you to create flexible and reusable type definitions. As you continue working with TypeScript, you’ll find conditional types invaluable for building type-safe applications.

Practice with these patterns and examples to become more comfortable with conditional types. Remember to start simple and gradually work your way up to more complex use cases as your understanding grows.

For more TypeScript insights, check out our guide on TypeScript Union Types vs Intersections which complements this knowledge perfectly.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share via
Copy link
Powered by Social Snap