Typescript Mapped Types And Conditional Types Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    11 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of TypeScript Mapped Types and Conditional Types

TypeScript Mapped Types and Conditional Types

Mapped Types

Introduction: Mapped types are a powerful feature in TypeScript that allow you to create new types by transforming existing ones. They are particularly useful for transforming the shape of objects in a declarative manner. Mapped types have the form:

type NewType<T> = {
    [K in keyof T]: SomeType;
};

Key Features:

  1. Key Remapping: You can remap keys using template literal types.

    type RemappedKeys<T> = {
        [K in keyof T as `new_${string & K}`]: T[K];
    };
    
  2. Modifiers: Mapped types allow you to add or remove readonly and ? (optional) modifiers.

    type ReadonlyKeys<T> = {
        readonly [K in keyof T]: T[K];
    };
    
    type OptionalKeys<T> = {
        [K in keyof T]?: T[K];
    };
    
  3. Key Filtering: You can filter keys using the extends keyword.

    type FilteredKeys<T> = {
        [K in keyof T as K extends string ? (K extends `_${string}` ? never : K) : K]: T[K];
    };
    

Conditional Types

Introduction: Conditional types allow you to create types that depend on the conditions of other types. They have the form:

type NewType<T> = T extends Type1 ? Type2 : Type3;

Key Features:

  1. Type Guard: Conditional types can act as type guards, ensuring that certain operations are safe.

    type IsNumber<T> = T extends number ? true : false;
    
  2. Distributive Behavior: When a conditional type acts on a union type, it distributes over that type, evaluating each member individually.

    type DistributedType<T> = T extends string ? 'string' : 'not string';
    // For T = string | number
    // DistributedType<T> => 'string' | 'not string'
    
  3. Infer Keyword: The infer keyword allows you to capture part of a type for later use.

    type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
    
  4. Type Constraints: You can specify constraints to限定 the types that can be used with conditional types.

    type CheckType<T extends string | number> = T extends number ? 'number' : 'string';
    

Combining Mapped Types and Conditional Types:

You can leverage the power of both mapped and conditional types to create complex type transformations. Here's an example of a utility type that converts all properties of an object to readonly if they are strings:

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

type Example = {
    prop1: string;
    prop2: number;
    prop3: string;
};

type Result = ReadonlyStringProps<Example>;
// Result => { prop1: readonly string; prop3: readonly string; }

Summary:

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement TypeScript Mapped Types and Conditional Types


Understanding TypeScript Mapped Types

What are Mapped Types?

Mapped types allow you to create new types by transforming existing ones. They're useful when you want to map the properties of an object type to a new property type.

Basic Syntax

The basic syntax for a mapped type looks like this:

type NewType = {
  [Key in keyof ExistingType]: NewValueType;
};

Here's what each part means:

  • [Key in keyof ExistingType]: Iterates over all keys in ExistingType.
  • NewValueType: The type that each key in ExistingType will have in NewType.

Example: Simple Mapped Type

Let's start with a simple example where we create a mapped type that converts all properties of an object type to optional.

Step 1: Define the Original Type

type User = {
  name: string;
  age: number;
  isActive: boolean;
};

Step 2: Create the Mapped Type

type PartialUser = {
  [Key in keyof User]?: User[Key];
};

In this example, PartialUser will have name, age, and isActive as optional properties.

Step 3: Use the Mapped Type

const partialUser: PartialUser = {
  name: "Alice", // Note: Only 'name' is required here.
};

Example: Adding Read-Only Properties

Now let's create a mapped type where all properties of an object type are read-only.

Step 1: Define the Original Type

We'll use the same User type from the previous example.

type User = {
  name: string;
  age: number;
  isActive: boolean;
};

Step 2: Create the Mapped Type

type ReadonlyUser = {
  readonly [Key in keyof User]: User[Key];
};

Step 3: Use the Mapped Type

const readonlyUser: ReadonlyUser = {
  name: "Bob",
  age: 30,
  isActive: true,
};

// This will cause an error because we made all properties read-only.
readonlyUser.name = "Changed"; // Error: Cannot assign to 'name' because it is a read-only property.

Example: Modifying Property Keys

Let's create a mapped type that adds the suffix _status to each property name.

Step 1: Define the Original Type

type User = {
  name: string;
  age: number;
  isActive: boolean;
};

Step 2: Create the Mapped Type with Modified Keys

type UserStatus = {
  [Key in keyof User as `${Key}_status`]: User[Key];
};

Note: The as keyword is used in newer versions of TypeScript (4.1 and above) to rename or transform keys during mapping.

Step 3: Use the Mapped Type

const userStatus: UserStatus = {
  name_status: "Alice",
  age_status: 25,
  isActive_status: false,
};

console.log(userStatus);

Understanding TypeScript Conditional Types

What are Conditional Types?

Conditional types enable you to express non-uniform type mappings based on conditions. They're powerful for creating flexible types that vary based on other types.

Basic Syntax

The basic syntax for a conditional type looks like this:

type NewType<T> = T extends Condition ? TrueType : FalseType;

Here's what each part means:

  • T: The generic type parameter.
  • Condition: The condition that T must extend.
  • TrueType: The type that NewType will be if T satisfies the Condition.
  • FalseType: The type that NewType will be if T does not satisfy the Condition.

Example: Basic Conditional Type

Let's create a conditional type that checks if a given type is a string and returns true, otherwise it returns false.

Step 1: Define the Conditional Type

type IsString<T> = T extends string ? true : false;

Step 2: Use the Conditional Type

type IsStr = IsString<string>; // true
type IsNum = IsString<number>; // false
type IsArr = IsString<string[]>; // false

console.log(IsStr, IsNum, IsArr); // true false false

Example: Creating a Union of Properties Based on Value Type

Now let's create a more complex conditional type that extracts property names from an object type where the properties are of a specific type (e.g., string).

Step 1: Define the Original Type

type Product = {
  id: number;
  name: string;
  price: number;
  description?: string;
};

Step 2: Create the Conditional Type

type StringKeys<T> = {
  [Key in keyof T]: T[Key] extends string ? Key : never;
}[keyof T];

In this example:

  • The first level { [Key in keyof T]: T[Key] extends string ? Key : never } creates a type where property keys are mapped to themselves if their values are strings, otherwise they're mapped to never.
  • The second level [keyof T] flattens the object type into a union of its keys, effectively removing never from the union.

Step 3: Use the Conditional Type

type ProductStringKeys = StringKeys<Product>;

// ProductStringKeys will be either 'name' | 'description'.
const productName: ProductStringKeys = 'name';
const productDescription: ProductStringKeys = 'description';

console.log(productName, productDescription); // name description

Example: Conditional Types with Mapped Types

Let's use both mapped types and conditional types to create a new type that adds an extra modified prefix to the property names only if the property value is a string.

Step 1: Define the Original Type

type Book = {
  title: string;
  author: string;
  yearPublished: number;
};

Step 2: Create the Conditional Mapped Type

type ConditionalModifiedKeys<T> = {
  [Key in keyof T as T[Key] extends string ? `modified_${Key}` : Key]: T[Key];
};

Here:

  • The as keyword in [Key in keyof T as T[Key] extends string ? modified_${Key} : Key] renames the keys if they are of type string.
  • T[Key] remains the same for other types.

Step 3: Use the Conditional Mapped Type

type ModifiedBook = ConditionalModifiedKeys<Book>;

// ModifiedBook will have keys 'modified_title', 'modified_author', and 'yearPublished'.
const modifiedBook: ModifiedBook = {
  modified_title: "The Great Gatsby",
  modified_author: "F. Scott Fitzgerald",
  yearPublished: 1925,
};

console.log(modifiedBook);

Putting It All Together: Combining Mapped and Conditional Types

Let's create a complex example that combines both mapped and conditional types. We'll define a mapped type that makes properties optional if they are of type number.

Example: Optional Numerical Properties

Step 1: Define the Original Type

type Employee = {
  id: number;
  name: string;
  department: string;
  salary: number;
};

Step 2: Create the Combined Mapped Type

type OptionalNumericProperties<T> = {
  [Key in keyof T]: Key extends keyof AnyNumberObject ? T[Key] | undefined : T[Key];
};

type AnyNumberObject = { [K: string]: number };

Here:

  • OptionalNumericProperties maps through each key in T.
  • It then uses a conditional type to check if Key exists in AnyNumberObject (an object with any string keys and numeric values).
  • If Key is a numerical property, it makes it T[Key] | undefined (optional), else T[Key] remains the same.

Step 3: Use the Combined Mapped Type

type PartialSalaryEmployee = OptionalNumericProperties<Employee>;

const partialSalaryEmployee: PartialSalaryEmployee = {
  id: 1,
  name: "Charlie",
  department: "Engineering",
  // salary is now optional
  salary: undefined,
};

console.log(partialSalaryEmployee);

Real-World Use Case: Transforming API Responses

Let's use mapped and conditional types to transform an API response into a more manageable format. Suppose we receive an API response where each property value can be either the actual data or an Error object.

Example: Transforming API Response

Step 1: Define the Original Type

type ApiResponse = {
  userId: number | Error; // ID can be a number or an error
  userName: string | Error; // Name can be a string or an error
  userEmail: string | Error; // Email can be a string or an error
};

Step 2: Create the Mapped Type to Extract Data

type DataExtractor<T> = {
  [Key in keyof T]: T[Key] extends Error ? never : T[Key];
} & {
  [Key in keyof T]: T[Key] extends Error ? never : Key;
};

Wait, that doesn't work correctly in this form! Let's refine it to two separate types:

type ErrorKeys<T> = {
  [Key in keyof T]: T[Key] extends Error ? Key : never;
}[keyof T];

type DataKeys<T> = {
  [Key in keyof T]: T[Key] extends Error ? never : Key;
}[keyof T];

type SuccessResponse<T> = {
  [Key in DataKeys<T>]: T[Key];
};

type ErrorResponse<T> = {
  [Key in ErrorKeys<T>]: T[Key];
};

Step 3: Use the Combined Mapped and Conditional Types

const apiResponse: ApiResponse = {
  userId: 2,
  userName: new Error("Invalid name"),
  userEmail: "charlie@example.com",
};

// Extract successful and erroneous keys
type SuccessfulData = SuccessResponse<ApiResponse>;
type ErroneousData = ErrorResponse<ApiResponse>;

const successData: SuccessfulData = {
  userId: apiResponse.userId as number,
  userEmail: apiResponse.userEmail,
};

const errorData: ErroneousData = {
  userName: apiResponse.userName as Error,
};

console.log(successData, errorData);

Conclusion

TypeScript Mapped Types and Conditional Types are powerful tools for defining flexible and reusable type structures. By understanding how to use them, you can create more robust and maintainable code. Practice with these examples to get a better grasp of their capabilities, and gradually apply them to your own projects.

Top 10 Interview Questions & Answers on TypeScript Mapped Types and Conditional Types

1. What are Mapped Types in TypeScript?

Answer: Mapped Types allow you to create new types by transforming existing ones through a template-like process. They iterate over each key of an existing type and apply a transformation to each key and its value. For example:

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Person = {
  name: string;
  age: number;
};

type ReadonlyPerson = Readonly<Person>; // Results in a Person type with all properties made readonly

2. How do Conditional Types work in TypeScript?

Answer: Conditional Types are a powerful feature for creating types that depend on conditions. They use the syntax T extends U ? X : Y. Here’s an example:

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";

type T0 = TypeName<string>;  // "string"
type T1 = TypeName<() => void>;  // "function"

3. Can you provide an example combining Mapped and Conditional Types?

Answer: Here’s a combined example where we toggle readonly attributes based on whether values are of type number:

type MakeNumberKeysReadWrite<T> = {
  // Mapped over keys of T
  [P in keyof T]: // Use keyof to iterate over keys of T
    T[P] extends number ? // If the type of property is number
    T[P] : // then just stay a number
    readonly T[P] // otherwise make it read-only
};

type Person = {
  name: string;
  age: number;
};

type MutablePerson = MakeNumberKeysReadWrite<Person>; // 'age' is mutable, 'name' is readonly

4. What is the difference between modifying existing types and adding properties in Mapped Types?

Answer: In Mapped Types, modifying existing types involves changing the properties of an existing interface via a template. Adding new properties implies adding entirely new keys to a type, which might be done via intersection or merging types. Here’s how to modify:

type ModifyExistingType<T> = {
  [P in keyof T]: T[P] extends string ? number : T[P];
};

And to add properties:

type AddProperty<T> = T & {
  newProp: string;
};

5. How do you use the -?/+? modifiers in Mapped Types?

Answer: The -? and +? modifiers are used to add or remove optionality (?) from properties in Mapped Types. Here’s an example:

type Concrete<T> = {
  [P in keyof T]-?: T[P]; // Removes optionality (Make all properties non-optional)
};

type PartialPerson = {
  name?: string;
  age?: number;
};

type ConcretePerson = Concrete<PartialPerson>; // Both name and age are now required

6. Can you explain the never type in the context of Mapped Types?

Answer: The never type is often used in Mapped Types to exclude keys. For example, you can filter keys based on a condition, where keys not meeting the condition are set to never, effectively excluding them:

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

type User = {
  name: string;
  id: number;
  isActive: boolean;
};

type NumericAndBooleanUserFields = NonStringKeys<User>; // Only 'id' and 'isActive' exist

7. How does the keyof operator work in Mapped Types?

Answer: The keyof operator extracts the keys of a type as a union of literal types. In the context of Mapped Types, keyof is crucial to iterate over the keys of an existing type while applying transformations. Here’s a simple example:

type Book = {
  title: string;
  author: string;
};

type BookKeys = keyof Book; // "title" | "author"

8. Can Mapped Types be recursive?

Answer: While TypeScript does not natively support recursive Mapped Types, you can create types that recursively shape nested objects. This requires leveraging TypeScript’s conditional and indexed access types carefully. Here’s a simplified example:

type DeepReadonly<T> =
  T extends (infer U)[] ? DeepReadonlyArray<U> : // Check if T is an array
  T extends object ? DeepReadonlyObject<T> : // Check if T is an object
  T;

type DeepReadonlyArray<T> = ReadonlyArray<DeepReadonly<T>>;

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

9. What are some practical use cases for Mapped and Conditional Types?

Answer: Mapped and Conditional Types are highly practical for:

  • Creating utility types (like Readonly, Partial, Exclude, etc.).
  • Working with object mappings, creating efficient type transformations.
  • Enforcing constraints and enforcing type correctness in complex systems.
  • Generating type definitions from data schemas.
  • Crafting advanced library and framework APIs.

10. How do I use TypeScript’s infer keyword in Conditional Types?

Answer: The infer keyword is used in conjunction with extends to infer a type within a true branch of a conditional type. It’s useful for extracting types from generic types. Here’s an example:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

function computeSum(a: number, b: number): number {
  return a + b;
}

type SumResult = ReturnType<typeof computeSum>; // 'number'

In this case, infer R captures the return type of the function inside the conditional type.

You May Like This Related .NET Topic

Login to post a comment.