Advanced mapped type patterns and techniques.

Key Remapping with Filters

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

interface User {
  name: string;
  age: number;
}

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

Filter Properties by Type

type StringProperties<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

interface User {
  name: string;
  age: number;
  email: string;
}

type UserStrings = StringProperties<User>;
// { name: string; email: string; }

Remove Properties

type OmitByType<T, U> = {
  [K in keyof T as T[K] extends U ? never : K]: T[K];
};

interface User {
  name: string;
  age: number;
  email: string;
}

type UserWithoutStrings = OmitByType<User, string>;
// { age: number; }

Rename Properties

type Rename<T, K extends keyof T, N extends string> = Omit<T, K> & {
  [P in N]: T[K];
};

interface User {
  firstName: string;
  lastName: string;
}

type Renamed = Rename<User, "firstName", "first">;
// { first: string; lastName: string; }

Deep Readonly

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

interface User {
  name: string;
  address: {
    street: string;
    city: string;
  };
}

type ReadonlyUser = DeepReadonly<User>;
// {
//   readonly name: string;
//   readonly address: {
//     readonly street: string;
//     readonly city: string;
//   };
// }

Optionalize Properties

type Optionalize<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface User {
  name: string;
  age: number;
  email: string;
}

type PartialEmail = Optionalize<User, "email">;
// { name: string; age: number; email?: string; }

Map Values

type MapValues<T, F> = {
  [K in keyof T]: F extends (value: T[K]) => infer R ? R : never;
};

interface User {
  name: string;
  age: number;
}

type Stringified = MapValues<User, (v: any) => string>;
// { name: string; age: string; }

Exercises

Exercise 1: Key Transformation

// Create a mapped type that:
// - Transforms all keys to lowercase
// - Preserves values

Exercise 2: Filter and Transform

// Create a mapped type that:
// - Filters out function properties
// - Transforms remaining properties to optional

Exercise 3: Deep Optional

// Create a type 'DeepPartial' that:
// - Makes all properties optional recursively
// - Works with nested objects and arrays

Exercise 4: Property Prefix

// Create a mapped type that:
// - Adds a prefix to all property names
// - Example: { name: string } => { user_name: string }

Exercise 5: Complex Transformation

// Create a mapped type that:
// - Converts all string properties to number
// - Converts all number properties to string
// - Leaves other types unchanged

Best Practices

  1. Use key remapping - Transform keys when needed
  2. Filter properties - Use as never to remove properties
  3. Combine transformations - Chain mapped types
  4. Test with real types - Ensure they work correctly
  5. Document complex types - Explain the transformation

Common Mistakes

  1. Forgetting keyof in key remapping
// ❌ Wrong
type Bad<T> = {
  [K in T]: T[K]; // T is not a union type
};

// ✅ Correct
type Good<T> = {
  [K in keyof T]: T[K];
};
  1. Wrong filter syntax
// ❌ Wrong
type Bad<T> = {
  [K in keyof T]: T[K] extends string ? T[K] : never;
};

// ✅ Correct - use 'as never' to filter
type Good<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};
  1. Not handling nested types
// ❌ Only one level
type Shallow<T> = {
  [K in keyof T]?: T[K];
};

// ✅ Recursive
type Deep<T> = {
  [K in keyof T]?: T[K] extends object ? Deep<T[K]> : T[K];
};

Next Steps

Learn about Template Literal Types.