🔥 Advanced TypeScript Patterns (Part-6)

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.

You might also like