TypeScript - Conditional and Mapped Types
Last Updated :
23 Jul, 2025
TypeScript provides Conditional Types and Mapped Types to make type definitions more flexible and reusable. Conditional Types allow types to change based on conditions, while Mapped Types modify properties of an existing type. These features help handle complex data structures and improve type safety in code.
Conditional Types
Conditional Types in TypeScript allow you to create types based on a condition.
JavaScript
type IsStr<T> = T extends string ? "Yes" : "No";
type Test1 = IsStr<string>;
type Test2 = IsStr<number>;
Output
Yes
No
In this example
- IsStr
- <T> is a generic type that checks if T extends string. If so, it resolves to "Yes", otherwise to "No".
- Test1 will be "Yes" because string extends string.
- Test2 will be "No" because number does not extend string.
Mapped Types
Mapped Types allow you to create new types by transforming the properties of an existing type. This is useful when you want to modify all properties of an object, such as making them readonly, optional, or changing their types.
JavaScript
type Person = {
name: string;
age: number;
};
type obj = {
readonly [P in keyof Person]: Person[P];
};
const person: obj = {
name: "Alia",
age: 25,
};
person.name = "Bobby";
Output
Error: Cannot assign to 'name' because it is a read-only property.
In this example
- ReadOnlyPerson uses a mapped type to iterate over Person and apply readonly to each property.
- When trying to modify person.name, TypeScript throws an error because it's read-only.
Combining Conditional and Mapped Types
You can combine both concepts to create more powerful type transformations.
JavaScript
type MakeReadOnly<T> = {
readonly [P in keyof T]: T[P];
};
type MakeOptional<T> = {
[P in keyof T]?: T[P];
};
type User = {
name: string;
age: number;
};
type ConditionalType<T> = T extends { age: number } ? MakeReadOnly<T> : MakeOptional<T>;
type Test1 = ConditionalType<User>;
type Test2 = ConditionalType<{ name: string }>;
Output
{ readonly name: string; readonly age: number; }
{ name?: string }
In this example
- ConditionalType<T> checks if T has an age property.
- If yes, it makes all properties readonly.
- If no, it makes all properties optional.
- Test1 results in { readonly name: string; readonly age: number; } since User has age.
- Test2 results in { name?: string } since { name: string } doesn’t have age.
Practical Use Cases
1. Extracting Element Type from an Array : You can use conditional types to define utility functions that change behavior based on the type of input. For instance, you can create a type that only returns a valid response for a specific data type.
JavaScript
type ElemType<T> = T extends (infer U)[] ? U : T;
type StrArray = string[];
type NumArray = number[];
type StrElement = ElemType<StrArray>;
type NumElement = ElemType<NumArray>;
In this example
- ElemType<T> extracts the element type of an array.
- If T is an array (T extends (infer U)[]), it returns the element type U.
- Otherwise, it returns T itself.
2. Applying Default Values to Object Properties : Mapped types are particularly useful when you want to apply transformations to all properties of a type, such as making them all optional or setting them to a default value.
JavaScript
type DefaultProps<T> = {
[P in keyof T]: T[P] | null;
};
type Settings = {
theme: string;
notifications: boolean;
};
type DefaultSettings = DefaultProps<Settings>;
const defaultSettings: DefaultSettings = {
theme: null,
notifications: null,
};
In this example
- DefaultProps<T> modifies all properties of T to accept null.
- DefaultSettings will have properties that are string | null and boolean | null.
Best Practices for Using Conditional and Mapped Types
- Keep Types Simple: Avoid excessive nesting of conditional and mapped types to improve readability.
- Use Readable Type Names: Use descriptive names like MakeReadOnly<T> instead of vague names.
- Combine Utility Types: Use mapped types to modify properties dynamically and combine them with conditional types for better flexibility.
- Test with Multiple Inputs: Always check how your types behave with different input types to avoid unexpected errors.
- Leverage Built-in Utility Types: TypeScript provides utility types like Readonly<T>, Partial<T>, and Required<T>, which can often replace custom implementations.