Master conditional types for advanced type transformations.
Complex Conditional Logic
type IsNever<T> = [T] extends [never] ? true : false;
type IsAny<T> = 0 extends 1 & T ? true : false;
type IsUnknown<T> = IsAny<T> extends true
? false
: unknown extends T
? true
: false;
Type Equality
type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
T
>() => T extends Y ? 1 : 2
? true
: false;
type Test1 = Equals<string, string>; // true
type Test2 = Equals<string, number>; // false
Extract Function Types
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
interface User {
name: string;
greet(): void;
age: number;
sayHello(): string;
}
type UserFunctions = FunctionProperties<User>;
// { greet(): void; sayHello(): string; }
Deep Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface User {
name: string;
address: {
street: string;
city: string;
};
}
type PartialUser = DeepPartial<User>;
// {
// name?: string;
// address?: {
// street?: string;
// city?: string;
// };
// }
Required Keys
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
interface User {
name: string;
age?: number;
email?: string;
}
type Required = RequiredKeys<User>; // "name"
Optional Keys
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
interface User {
name: string;
age?: number;
email?: string;
}
type Optional = OptionalKeys<User>; // "age" | "email"
Exclude Null and Undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
Extract Promise Type
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type T0 = Awaited<Promise<string>>; // string
type T1 = Awaited<Promise<Promise<number>>>; // number
Union to Intersection
type UnionToIntersection<U> = (
U extends any ? (x: U) => void : never
) extends (x: infer I) => void
? I
: never;
type T0 = UnionToIntersection<{ a: string } | { b: number }>;
// { a: string } & { b: number }
Exercises
Exercise 1: Deep Required
// Create a type 'DeepRequired' that:
// - Makes all properties required recursively
// - Works with nested objects
Exercise 2: Type Equality
// Create a type 'NotEqual' that:
// - Returns true if types are not equal
// - Returns false if types are equal
Exercise 3: Extract Nested Types
// Create a type that extracts:
// - All function return types from an object
// - As a union type
Exercise 4: Conditional Mapping
// Create a type that:
// - Maps over object properties
// - Converts functions to their return types
// - Leaves other properties unchanged
Exercise 5: Complex Conditional
// Create a type 'Flatten' that:
// - Flattens nested object types
// - Example: { a: { b: string } } => { a_b: string }
Best Practices
- Understand distribution - Conditional types distribute over unions
- Use infer effectively - Extract types from complex structures
- Test your types - Ensure they work as expected
- Document complex logic - Add comments explaining the transformation
- Combine with mapped types - Powerful type transformations
Common Mistakes
- Not preventing distribution when needed
// Distributes
type T = ToArray<string | number>; // string[] | number[]
// Prevent with brackets
type T = ToArrayNonDist<string | number>; // (string | number)[]
- Wrong infer placement
// ❌ Wrong
type Bad<T> = T extends Array<any> ? infer U : T;
// ✅ Correct
type Good<T> = T extends Array<infer U> ? U : T;
- Circular conditional types
// ❌ Might cause issues
type A<T> = T extends string ? B<T> : never;
type B<T> = T extends string ? A<T> : never;
Next Steps
Learn about Mapped Types (Advanced).