How to Use Conditional Types in TypeScript

Use conditional types in TypeScript when a type should change based on another type’s shape or value relationship. They are perfect for reusable utilities, API helpers, schema transforms, and advanced library typing.

What you’ll build or solve

You’ll learn how to use conditional types in TypeScript with extends, infer, and reusable generic utilities. You’ll also know when they are better than unions alone.

When this approach works best

This approach is the right choice when generic type behavior depends on input types.

Common real-world scenarios include:

  • API response helpers
  • Promise unwrapping
  • Nullable transforms
  • Function return extraction
  • Library utility types

This is a bad idea when a simple union or overload communicates the intent more clearly.

Prerequisites

You only need:

  • Strong TypeScript generics knowledge
  • extends constraints
  • Familiarity with utility types

Step-by-step instructions

Step 1: Create a basic conditional type

The core syntax is:

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

This checks whether T matches string.

type A = IsString<string>;
type B = IsString<number>;

This becomes:

  • A = true
  • B = false

Step 2: Use conditional types for reusable transforms

A common pattern is nullable cleanup.

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

This removes nullish values from unions.

It is great for reusable library helpers.

Step 3: Use infer to extract nested types

infer makes conditional types much more powerful.

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

This extracts function return types automatically.

type Result = GetReturnType<() => number>;

This becomes number.

Step 4: Unwrap promises

A very practical use case is promise extraction.

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

This is excellent for async helpers.

What to look for:

  • Use T extends X ? A : B
  • Great for reusable generic utilities
  • infer extracts nested types
  • Excellent for async and function helpers
  • Keep them readable

Examples you can copy

String check

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

Promise unwrap

type Data = UnwrapPromise<Promise<User>>;

Return extraction

type Result = GetReturnType<typeof loadUser>;

Common mistakes and how to fix them

Mistake 1: Using conditional types for simple unions

What the reader might do:

Create complex conditional logic for tiny APIs.

Why it breaks: readability drops.

Corrected approach:

Use unions or overloads first.

Mistake 2: Forgetting distributive behavior on unions

What the reader might do:

Apply a conditional type to string | number.

Why it breaks: the result distributes across the union.

Corrected approach:

Use tuple wrappers when distribution is unwanted.

Mistake 3: Overusing nested infer chains

What the reader might do:

Build deeply nested conditional helpers.

Why it breaks: maintenance becomes hard.

Corrected approach:

Break utilities into smaller named helpers.

Troubleshooting

If unions split unexpectedly, check distributive behavior.

If extraction fails, verify the infer position.

If the helper feels unreadable, simplify the generic.

If the use case is basic, use overloads instead.

Quick recap

  • Use extends ? : for conditional types
  • Great for reusable generic transforms
  • Use infer to extract nested types
  • Excellent for promise and function helpers
  • Keep utilities small and readable