TYPESCRIPT

TypeScript Conditional Types: Syntax, Behavior, and Practical Examples

TypeScript conditional types allow you to create types that depend on a condition. This advanced feature enables you to write logic directly in the type system, making your code more adaptable, expressive, and powerful. You can dynamically map input types to output types, check for properties, filter unions, and much more.

By using conditional types TypeScript provides the ability to simulate branching and inference without duplicating type definitions. You’ll often see them in libraries, frameworks, and reusable utility types.

This guide explains how conditional types work, how to use them in real-world scenarios, and how they relate to functions, unions, and infer keywords.


What Are Conditional Types in TypeScript?

A conditional type uses the syntax:

A extends B ? X : Y

This means: if A is assignable to B, then the type is X; otherwise, it is Y.

Example:

type IsString<T> = T extends string ? "Yes" : "No";

type A = IsString<string>; // "Yes"
type B = IsString<number>; // "No"

This shows the basic structure of TypeScript conditional types, allowing you to check and branch types based on their relationships.


Conditional Types with Generics

One of the key powers of conditional types TypeScript offers is their integration with generics. You can make conditional decisions based on type parameters.

type Response<T> = T extends string ? string : number;

const val1: Response<string> = "ok";   // Valid
const val2: Response<boolean> = 404;   // Valid

This design lets you define output types based on input types without manually creating every variant.


Working with Type Relationships

Conditional types can express subtype and assignability relationships.

Example:

type IsObject<T> = T extends object ? true : false;

type A = IsObject<{}>;         // true
type B = IsObject<null>;       // false
type C = IsObject<string[]>;   // true

Here, conditional type TypeScript logic helps evaluate complex structures and return different results accordingly.


Using infer with Conditional Types

You can extract inner types using infer, a special keyword used exclusively within conditional types.

type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<number[]>; // number
type B = ElementType<string>;   // string

This allows dynamic type extraction without manually referencing subtypes, making it ideal for reusable patterns.


Conditional Types and Return Values

You can define return types based on input conditions using typescript conditional return type logic.

type Return<T> = T extends () => infer R ? R : never;

type A = Return<() => number>;   // number
type B = Return<string>;         // never

This helps infer return types of functions automatically, reducing duplication and ensuring correctness.


Filtering Union Types

Conditional types distribute over unions. This is one of the most powerful features of TypeScript conditional types.

type NonString<T> = T extends string ? never : T;

type A = NonString<string | number | boolean>; // number | boolean

Here, string gets filtered out, and the result is a narrowed union. You can use this to create complex exclusion or inclusion logic.


Creating Utility Types

Many built-in TypeScript utility types are implemented using conditional types. You can create similar types to customize behavior.

Exclude

type MyExclude<T, U> = T extends U ? never : T;

type A = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"

Extract

type MyExtract<T, U> = T extends U ? T : never;

type B = MyExtract<"a" | "b" | "c", "a" | "c">; // "a" | "c"

These typescript conditional types examples mirror TypeScript’s Exclude and Extract types with the same internal logic.


Nested Conditional Types

You can nest conditions for more complex logic trees.

type Identify<T> =
  T extends string ? "String" :
  T extends number ? "Number" :
  T extends boolean ? "Boolean" :
  "Unknown";

type A = Identify<true>;    // "Boolean"
type B = Identify<123>;     // "Number"
type C = Identify<null>;    // "Unknown"

While readable for simple cases, deeply nested conditional types can become hard to maintain, so consider limiting nesting depth.


Conditional Types with Interfaces and Objects

You can also check property existence inside objects or interfaces.

type HasId<T> = T extends { id: number } ? true : false;

type A = HasId<{ id: number; name: string }>; // true
type B = HasId<{ name: string }>;             // false

This allows static type assertions based on the shape of objects and is useful in large schema-driven applications.


Use in React Component Props

In React projects with TypeScript, conditional types can enhance prop-based logic.

type Props<T> = T extends "input" ? { value: string } : { children: React.ReactNode };

const Component = <T extends "input" | "div">(props: Props<T>) => { ... };

This pattern allows a single component to change its prop types based on the T tag.


Type Constraints with Conditional Types

You can build safety rules using constraints and conditional fallback types.

type SafeKey<T> = T extends string | number | symbol ? T : never;

type A = SafeKey<"name">;   // "name"
type B = SafeKey<boolean>;  // never

This restricts key types to those valid in object definitions, improving robustness of utilities like Record.


Avoiding Common Pitfalls

When working with TypeScript conditional types, keep the following in mind:

  • Conditional types distribute across unions unless you wrap them in square brackets.
  • Overusing conditional types can reduce code clarity. Keep the logic readable.
  • Use infer sparingly and clearly, especially in reusable type helpers.

Conditional Types and Optional Fields

You can make optional types based on some condition.

type OptionalIf<T, U> = T extends U ? T | undefined : T;

type A = OptionalIf<"admin", "admin">; // "admin" | undefined
type B = OptionalIf<"user", "admin">;  // "user"

This is handy when certain fields are optional only under specific rules.


Combining with Mapped Types

You can use conditional types inside mapped types to transform keys conditionally.

type MarkReadonly<T> = {
  [K in keyof T]: T[K] extends string ? Readonly<T[K]> : T[K];
};

type Input = {
  name: string;
  age: number;
};

type Output = MarkReadonly<Input>; // name becomes readonly, age stays mutable

This technique enables dynamic remapping of object structures.

Learn TypeScript for Free
Start learning now
button icon
To advance beyond this tutorial and learn TypeScript by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster