Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're an architect and you've created a detailed blueprint for a house. This blueprint lists every room: the kitchen, the bedroom, the bathroom, and the garage. Now, imagine your client wants a separate "Maintenance Checklist" for that house. Instead of manually writing a new list, you decide to create a rule: "For every room listed in the blueprint, create a corresponding checkbox on the checklist."

If you add a "Sunroom" to the blueprint later, the checklist automatically gets a "Sunroom" checkbox because the rule is tied to the blueprint itself. You aren't defining the checklist manually; you're mapping the blueprint onto a new format.

In TypeScript, Mapped Types are exactly that. They let you take an existing type and "loop" through its keys to create a new type. It’s essentially the .map() method, but for your type system.

Turning a User Model into Form State

I see this all the time in large-scale React or Vue projects. You have a clean data model for a User, but the form used to edit that user needs more metadata for every single field—like whether the field has been touched or if it's currently validating.

type User = {
  id: string;
  username: string;
  email: string;
  age: number;
};

// Instead of manually writing this:
type UserFormState = {
  id: { value: string; isDirty: boolean };
  username: { value: string; isDirty: boolean };
  email: { value: string; isDirty: boolean };
  age: { value: number; isDirty: boolean };
};

Writing that manually is a nightmare. If you add a phoneNumber to the User type, you'll inevitably forget to update the UserFormState, and your app will crash or throw a type error in some obscure component. Here is how we do it with a mapped type:

type FormField<T> = {
  value: T;
  isDirty: boolean;
};

type UserFormState = {
  [K in keyof User]: FormField<User[K]>;
};

Let's break down that weird syntax. keyof User gives us a union of the keys: "id" | "username" | "email" | "age". The K in ... part is the loop. For every key (K) in that union, TypeScript creates a property. User[K] is a lookup type; it grabs the actual type of that specific property (e.g., when K is "age", User[K] is number).

Toggling Modifiers on the Fly

Sometimes you don't want to change the value type, but you want to change the behavior of the property. TypeScript lets you add or remove modifiers like readonly or the optional ? during the mapping process.

I often use this when I want to ensure that a "Configuration" object is strictly read-only after it's been initialized. You can use the + or - prefixes to add or remove these modifiers.

type Options = {
  endpoint: string;
  retries?: number;
  timeout: number;
};

// Make everything required and read-only
type StrictOptions = {
  readonly [K in keyof Options]-?: Options[K];
};

/* 
Result:
{
  readonly endpoint: string;
  readonly retries: number; // The '?' is gone!
  readonly timeout: number;
}
*/

The -? is the magic trick here. It explicitly tells TypeScript: "Whatever optionality existed on the original key, strip it away."

Renaming Keys During the Map

In the latest versions of TypeScript, we can actually change the name of the keys as we map them using the as keyword. This is incredibly powerful for creating "API Response" types where the backend sends you user_name (snake_case), but you want your frontend types to be userName (camelCase).

type UserApi = {
  user_id: string;
  user_email: string;
};

type UserFrontend = {
  [K in keyof UserApi as K extends `user_${infer Rest}` ? Capitalize<Rest> : K]: UserApi[K];
};

// Result: { id: string; email: string; } 
// (Wait, I simplified the logic slightly for the example, but you get the gist!)

Mapping types effectively means you stop writing the same type three different times. You define the "Source of Truth" once, and let the compiler derive everything else.




📋 Practical Task

Build a Product Validation Error Map

You are building an e-commerce admin panel. You have a Product interface, and you need to create a type called ProductValidationErrors. This type should have the exact same keys as the Product interface, but every single value must be string | null (where the string is the error message, and null means there is no error).

Requirements:

  • Define a Product interface with at least four fields (e.g., sku, price, title, description) with various types (string, number).
  • Use a Mapped Type to create ProductValidationErrors.
  • Ensure that if a new field is added to Product, ProductValidationErrors updates automatically.
interface Product {
  // Your fields here
}

type ProductValidationErrors = {
  // Your mapped type logic here
};

// Test it:
const errors: ProductValidationErrors = {
  // Try assigning a number here; it should fail.
  // Try assigning a string or null; it should pass.
};
Rating
0 0

There are no comments for now.

to be the first to leave a comment.