- Abstract class
- Annotations
- Array
- Asserts
- Casting
- Class
- Conditional types
- Const
- Date object
- Decorators
- Default parameter
- Dictionary
- Enum
- Exclude type
- Extract type
- For loop
- forEach()
- Function
- Generics
- Index signature
- Infer
- Inheritance
- Interface
- Keyof type operator
- Let
- Map type
- Mixin
- Module
- Namespace
- Never
- Object type
- Omit type
- Operator
- Optional parameter
- Partial type
- Pick type
- Promise
- Property
- Readonly type
- Record type
- Required type
- Satisfies operator
- Tuples
- Type alias
- Type assertion
- Type guard
- Type narrowing
- typeof Type Operator
- Union
- Utility types
- Var
- Void
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.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.