Skip to main content

Advanced Type Patterns in TypeScript

Created: May 8, 2026 Larry Qu 7 min read

Advanced type patterns enable sophisticated type manipulation. This article covers utility types, conditional types, and advanced inference.

Introduction

Advanced type patterns provide:

  • Type manipulation
  • Code reusability
  • Type safety
  • Flexibility
  • Maintainability

Understanding these helps you:

  • Create powerful types
  • Build reusable utilities
  • Maintain type safety
  • Reduce code duplication
  • Build scalable systems

Utility Types

Common Utility Types

// ✅ Good: Partial - make all properties optional
type User = { name: string; age: number; email: string };
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string }

// ✅ Good: Required - make all properties required
type RequiredUser = Required<PartialUser>;
// { name: string; age: number; email: string }

// ✅ Good: Readonly - make all properties readonly
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; readonly email: string }

// ✅ Good: Pick - select specific properties
type UserPreview = Pick<User, 'name' | 'email'>;
// { name: string; email: string }

// ✅ Good: Omit - exclude specific properties
type UserWithoutEmail = Omit<User, 'email'>;
// { name: string; age: number }

// ✅ Good: Record - create object with specific keys
type Status = 'active' | 'inactive' | 'pending';
type StatusCount = Record<Status, number>;
// { active: number; inactive: number; pending: number }

// ✅ Good: Exclude - exclude types from union
type NonString = Exclude<string | number | boolean, string>;
// number | boolean

// ✅ Good: Extract - extract types from union
type StringOrNumber = Extract<string | number | boolean, string | number>;
// string | number

// ✅ Good: ReturnType - get function return type
function add(a: number, b: number): number {
  return a + b;
}
type AddReturn = ReturnType<typeof add>;
// number

// ✅ Good: Parameters - get function parameter types
type AddParams = Parameters<typeof add>;
// [a: number, b: number]

// ✅ Good: InstanceType - get class instance type
class User {
  name: string;
}
type UserInstance = InstanceType<typeof User>;
// User

Conditional Types

Basic Conditional Types

// ✅ Good: Basic conditional type
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<42>; // false

// ✅ Good: Conditional with union
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number

// ✅ Good: Nested conditionals
type DeepFlat<T> = T extends Array<infer U>
  ? U extends Array<infer V>
    ? V
    : U
  : T;

type Result = DeepFlat<number[][]>; // number

Conditional Type Inference

// ✅ Good: Infer in conditional types
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Num = GetReturnType<() => number>; // number
type Str = GetReturnType<() => string>; // string

// ✅ Good: Extract function parameters
type GetParams<T> = T extends (...args: infer P) => any ? P : never;

type Params = GetParams<(a: string, b: number) => void>;
// [a: string, b: number]

// ✅ Good: Extract Promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;

type PromiseString = Awaited<Promise<string>>; // string
type PlainNumber = Awaited<number>; // number

Mapped Types

Basic Mapped Types

// ✅ Good: Basic mapped type
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type User = { name: string; age: number };
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }

// ✅ Good: Setters
type Setters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type UserSetters = Setters<User>;
// { setName: (value: string) => void; setAge: (value: number) => void }

// ✅ Good: Readonly mapping
type ReadonlyProps<T> = {
  readonly [K in keyof T]: T[K];
};

type ReadonlyUser = ReadonlyProps<User>;
// { readonly name: string; readonly age: number }

Advanced Mapped Types

// ✅ Good: Conditional mapped type
type Getters<T> = {
  [K in keyof T as T[K] extends Function ? never : `get${Capitalize<string & K>}`]: () => T[K];
};

// ✅ Good: Filter properties by type
type StringPropertiesOnly<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type User = { name: string; age: number; email: string };
type UserStrings = StringPropertiesOnly<User>;
// { name: string; email: string }

// ✅ Good: Create getters for specific types
type GettersForStrings<T> = {
  [K in keyof T as T[K] extends string ? `get${Capitalize<string & K>}` : never]: () => T[K];
};

type UserStringGetters = GettersForStrings<User>;
// { getName: () => string; getEmail: () => string }

Template Literal Types

Basic Template Literal Types

// ✅ Good: Template literal types
type EventName = `on${Capitalize<'click' | 'change' | 'submit'>}`;
// 'onClick' | 'onChange' | 'onSubmit'

// ✅ Good: Generate property names
type Getters<T extends Record<string, any>> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

// ✅ Good: Create event handlers
type EventHandlers<T extends Record<string, any>> = {
  [K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};

type User = { name: string; age: number };
type UserHandlers = EventHandlers<User>;
// { onName: (value: string) => void; onAge: (value: number) => void }

Type Inference Patterns

Advanced Inference

// ✅ Good: Infer from object structure
type InferObject<T> = T extends { data: infer D } ? D : never;

type Result = InferObject<{ data: string; status: number }>;
// string

// ✅ Good: Infer from array
type InferArray<T> = T extends (infer U)[] ? U : never;

type Item = InferArray<string[]>;
// string

// ✅ Good: Infer from function
type InferFunction<T> = T extends (...args: any[]) => infer R ? R : never;

type Result = InferFunction<(a: number) => string>;
// string

// ✅ Good: Deep inference
type DeepInfer<T> = T extends { nested: { value: infer V } } ? V : never;

type Value = DeepInfer<{ nested: { value: number } }>;
// number

Practical Advanced Patterns

Type-Safe API Builder

// ✅ Good: Type-safe API builder
type ApiEndpoint = {
  path: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  request?: Record<string, any>;
  response: Record<string, any>;
};

type ApiBuilder<T extends Record<string, ApiEndpoint>> = {
  [K in keyof T]: (
    data: T[K]['request'] extends undefined ? void : T[K]['request']
  ) => Promise<T[K]['response']>;
};

const endpoints = {
  getUser: {
    path: '/users/:id',
    method: 'GET' as const,
    response: { id: number; name: string }
  },
  createUser: {
    path: '/users',
    method: 'POST' as const,
    request: { name: string; email: string },
    response: { id: number; name: string; email: string }
  }
};

type Api = ApiBuilder<typeof endpoints>;
// {
//   getUser: (data: void) => Promise<{ id: number; name: string }>;
//   createUser: (data: { name: string; email: string }) => Promise<{ id: number; name: string; email: string }>;
// }

Type-Safe Form Builder

// ✅ Good: Type-safe form builder
type FormField = {
  type: 'text' | 'number' | 'email' | 'checkbox';
  required?: boolean;
  validate?: (value: any) => boolean;
};

type FormSchema = Record<string, FormField>;

type FormValues<T extends FormSchema> = {
  [K in keyof T]: T[K]['type'] extends 'checkbox'
    ? boolean
    : T[K]['type'] extends 'number'
    ? number
    : string;
};

const userForm = {
  name: { type: 'text' as const, required: true },
  age: { type: 'number' as const },
  email: { type: 'email' as const, required: true },
  subscribe: { type: 'checkbox' as const }
};

type UserFormValues = FormValues<typeof userForm>;
// {
//   name: string;
//   age: number;
//   email: string;
//   subscribe: boolean;
// }

Best Practices

  1. Use utility types:
    // ✅ Good
    type PartialUser = Partial<User>;
    
    // ❌ Bad
    type PartialUser = { name?: string; age?: number };
    ```javascript
    
  2. Leverage conditional types:
    // ✅ Good
    type Flatten<T> = T extends Array<infer U> ? U : T;
    
    // ❌ Bad
    type Flatten<T> = T extends Array<any> ? any : T;
    ```javascript
    
  3. Use mapped types for consistency:
    // ✅ Good
    type Getters<T> = {
      [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
    };
    
    // ❌ Bad
    type Getters<T> = {
      getName: () => T['name'];
      getAge: () => T['age'];
    };
    ```javascript
    

Common Mistakes

  1. Over-complicating types:
    // ❌ Bad
    type Complex<T> = T extends any ? T : never;
    
    // ✅ Good
    type Simple<T> = T;
    ```javascript
    
  2. Not using infer:
    // ❌ Bad
    type GetReturn<T> = T extends (...args: any[]) => any ? any : never;
    
    // ✅ Good
    type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never;
    

Summary

Advanced type patterns are powerful. Key takeaways:

  • Use utility types
  • Leverage conditional types
  • Use mapped types
  • Apply template literals
  • Infer types effectively
  • Build type-safe APIs
  • Maintain readability
  • Document complex types

Next Steps

Resources

Comments

Share this article

Scan to read on mobile