Conditional Types
Basic Conditional Types
typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // falseNested Conditionals
typescript
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | null>; // stringDistributive Conditional Types
typescript
type ToArray<T> = T extends any ? T[] : never;
type Numbers = ToArray<number | string>; // number[] | string[]Mapped Types
Basic Mapped Types
typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Optional<T> = {
[P in keyof T]?: T[P];
};
type User = {
name: string;
age: number;
};
type ReadonlyUser = Readonly<User>;
type PartialUser = Optional<User>;Key Remapping
typescript
type Getters<T> = {
[P in keyof T as `get${Capitalize<P & string}}]: () => T[P];
};
type UserGetters = Getters<User>;
// {
// getName: () => string;
// getAge: () => number;
// }Template Literal Types
String Manipulation
typescript
type EventName<T extends string> = `on${Capitalize<T>}Change`;
type ClickEvent = EventName<'click'>; // "onClickChange"
type Combine<K, P> = K extends string ? P extends string ? `${K}.${P}` : never : never;
type Combined = Combine<'user', 'name'>; // "user.name"String Templates
typescript
type AllKeys<T> = T extends any ? T : never;
type Color = 'red' | 'blue' | 'green';
type Size = 'small' | 'medium' | 'large';
type ColorSize = `${Color}-${Size}`;
// "red-small" | "red-medium" | ... | "green-large"Utility Types
Built-in Utility Types
typescript
// Partial - Make all properties optional
type PartialUser = Partial<User>;
// Required - Make all properties required
type RequiredUser = Required<Partial<User>>;
// Readonly - Make all properties readonly
type ReadonlyUser = Readonly<User>;
// Pick - Select specific properties
type NameOnly = Pick<User, 'name'>;
// Omit - Remove specific properties
type AgeOnly = Omit<User, 'name' | 'email'>;
// Record - Create object type
type UserMap = Record<string, User>;
// Exclude - Remove from union
type Numbers = Exclude<string | number, string>;
// Extract - Keep from union
type OnlyStrings = Extract<string | number, string>;
// ReturnType - Get return type
type Func = () => string;
type Return = ReturnType<Func>; // stringCustom Utility Types
typescript
// DeepPartial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// DeepRequired
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
// DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// Nullable
type Nullable<T> = T | null;
// Maybe
type Maybe<T> = T | null | undefined;Type Guards
Type Guard Functions
typescript
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
function process(value: unknown) {
if (isString(value)) {
// TypeScript knows value is string
console.log(value.toUpperCase());
}
}Discriminated Unions
typescript
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return 0.5 * shape.base * shape.height;
}
}Branded Types
typescript
type USD = number & { readonly __brand: 'USD' };
type EUR = number & { readonly __brand: 'EUR' };
const usd = (amount: number): USD => amount as USD;
const eur = (amount: number): EUR => amount as EUR;
const addFunds = (a: USD, b: USD): USD => {
return usd((a + b) as number);
};
// Type-safe - can't mix currencies
const total = addFunds(usd(100), usd(50));
// addFunds(usd(100), eur(50)) // Error!Type Inference
Infer Keyword
typescript
type Unpromise<T> = T extends Promise<infer U> ? U : T;
type Result = Unpromise<Promise<string>>; // string
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Head = First<[1, 2, 3]>; // 1ReturnType Enhancement
typescript
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
T extends (...args: any) => Promise<infer R> ? R : never;
async function getData(): Promise<{ id: number }> {
return { id: 1 };
}
type Data = AsyncReturnType<typeof getData>; // { id: number }Advanced Patterns
Function Overloading
typescript
function combine(a: string, b: string): string;
function combine(a: number, b: number): number;
function combine(a: any, b: any): any {
return a + b;
}
const result1 = combine('hello', 'world'); // string
const result2 = combine(1, 2); // numberGeneric Constraints
typescript
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // numberConditional Inference
typescript
type Unwrap<T> =
T extends Array<infer U> ? U :
T extends Promise<infer U> ? U :
T;
type Unwrapped = Unwrap<string[]>; // string
type UnwrappedPromise = Unwrap<Promise<number>>; // numberBest Practices
- 1Prefer type inference: Let TypeScript infer when possible
- 2Use strict mode: Catch errors at compile time
- 3Avoid any: Use unknown instead
- 4Use utility types: Leverage built-in types
- 5Brand primitives: Add type safety to primitives
- 6Type guards: Narrow types properly
- 7Conditional types: For flexible type logic
Conclusion
Advanced TypeScript patterns enable type-safe, expressive code. Master these patterns to write robust applications.