Once you’re comfortable with utility types and generics, the next step is mastering advanced TypeScript patterns. These patterns are heavily used in React, Angular, and large-scale enterprise applications to build flexible, type-safe, and reusable abstractions.
In this part, we’ll cover:
keyof& index access types- Mapped Types
- Conditional Types with
infer - Building custom utility types
- Real-world React & Angular examples
1. keyof – Getting Keys of a Type
What it does
keyof produces a union of property names of a type.
interface User {
id: string;
name: string;
email: string;
}
type UserKeys = keyof User;
// "id" | "name" | "email"
Real-world use cases
- Dynamic form builders
- Table column definitions
- Validation rules
2. Index Access Types
What it does
Allows you to access the type of a specific property.
type UserName = User["name"]; // string
Combined with keyof
type ValueOf<T> = T[keyof T];
Use cases
- Generic helpers
- API response typing
3. Mapped Types
What they do
Mapped types create new types by transforming properties of an existing type.
type Optional<T> = {
[K in keyof T]?: T[K];
};
Equivalent to Partial<T>.
Example: Readonly fields except ID
type ReadonlyExceptId<T extends { id: any }> = {
readonly [K in keyof T]: T[K];
} & { id: T['id'] };
Real-world use cases
- Form states
- DTO transformations
- API layer contracts
4. Conditional Types
What they do
Conditional types allow type-level if/else logic.
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
5. Conditional Types with infer
What infer does
Extracts part of a type automatically.
Example: Get array item type
type ArrayItem<T> = T extends (infer U)[] ? U : never;
type Item = ArrayItem<string[]>; // string
Example: Promise result type
type PromiseResult<T> = T extends Promise<infer R> ? R : never;
6. Building Custom Utility Types
Example: DeepReadonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
Example: NonEmptyArray
type NonEmptyArray<T> = [T, ...T[]];
7. Real React Examples
Strongly typed component props
type ButtonProps = {
variant: 'primary' | 'secondary';
onClick: () => void;
};
function Button(props: Readonly<ButtonProps>) {
return <button>{props.variant}</button>;
}
Controlled form field helper
type FieldProps<T, K extends keyof T> = {
value: T[K];
onChange: (value: T[K]) => void;
};
8. Real Angular Examples
Typed FormGroup model
interface LoginForm {
email: string;
password: string;
}
type LoginFormControls = {
[K in keyof LoginForm]: FormControl<LoginForm[K]>;
};
Typed service response
getUser(): Observable<User> {
return this.http.get<User>('/api/user');
}
Why These Patterns Matter
- Enable advanced abstractions
- Make large refactors safer
- Improve IDE autocomplete
- Reduce runtime bugs
These patterns are used extensively in design systems, form engines, API SDKs, and UI builders.
What’s Next?
🚀 Part-7: TypeScript Best Practices for Large-Scale Applications
- Folder & type organization
- DTO vs Domain models
- Avoiding
any - Performance & compile-time tips
Final Thought
Advanced TypeScript patterns turn types into a powerful language for designing systems, not just annotating code.
