A comprehensive collection of advanced TypeScript mapped types that transform object types in powerful ways, enabling precise type manipulation for real-world applications.
// ============================================
// Advanced Mapped Types Collection
// ============================================
/**
* Mutable<T> - Removes readonly modifier from all properties
* Useful when you need to modify an object that was typed as immutable
*/
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// Usage: Mutable<T>
interface ImmutableUser {
readonly id: number;
readonly name: string;
readonly email: string;
}
type MutableUser = Mutable<ImmutableUser>;
// Result: { id: number; name: string; email: string; }
const mutableUser: MutableUser = { id: 1, name: "John", email: "john@example.com" };
mutableUser.name = "Jane"; // Now allowed!
/**
* Nullable<T> - Makes all properties nullable (can be null)
* Useful for representing data that might have missing values from APIs
*/
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// Usage: Nullable<T>
interface Product {
id: number;
name: string;
price: number;
description: string;
}
type NullableProduct = Nullable<Product>;
// Result: { id: number | null; name: string | null; price: number | null; description: string | null; }
const partialProduct: NullableProduct = {
id: 1,
name: "Widget",
price: null, // Price not yet determined
description: null // Description pending
};
/**
* Optional<T, K> - Makes only specified properties optional
* More granular than Partial<T> - targets specific keys
*/
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Usage: Optional<T, K>
interface Article {
id: number;
title: string;
content: string;
author: string;
publishedAt: Date;
}
type DraftArticle = Optional<Article, "publishedAt" | "id">;
// Result: { title: string; content: string; author: string; publishedAt?: Date; id?: number; }
const draft: DraftArticle = {
title: "TypeScript Tips",
content: "Here are some advanced tips...",
author: "Developer"
// id and publishedAt are now optional
};
/**
* RequireOnly<T, K> - Makes specified properties required, rest optional
* Inverse of Optional - useful for create/update patterns
*/
type RequireOnly<T, K extends keyof T> = Partial<Omit<T, K>> & Required<Pick<T, K>>;
// Usage: RequireOnly<T, K>
interface UserProfile {
id: number;
username: string;
email: string;
avatar: string;
bio: string;
website: string;
}
type CreateUserPayload = RequireOnly<UserProfile, "username" | "email">;
// Result: { id?: number; avatar?: string; bio?: string; website?: string; username: string; email: string; }
const newUser: CreateUserPayload = {
username: "johndoe",
email: "john@example.com"
// All other fields are optional during creation
};
/**
* DeepNonNullable<T> - Recursively removes null and undefined from all nested properties
* Essential for working with validated/sanitized data structures
*/
type DeepNonNullable<T> = T extends object
? T extends Array<infer U>
? Array<DeepNonNullable<U>>
: { [P in keyof T]-?: DeepNonNullable<NonNullable<T[P]>> }
: NonNullable<T>;
// Usage: DeepNonNullable<T>
interface ApiResponse {
user: {
id: number | null;
profile: {
name: string | undefined;
settings: {
theme: string | null;
notifications: boolean | undefined;
} | null;
} | undefined;
} | null;
metadata: {
timestamp: Date | null;
};
}
type ValidatedResponse = DeepNonNullable<ApiResponse>;
// All nested null/undefined are removed recursively
const validatedData: ValidatedResponse = {
user: {
id: 1,
profile: {
name: "John",
settings: {
theme: "dark",
notifications: true
}
}
},
metadata: {
timestamp: new Date()
}
};
/**
* PartialBy<T, K> - Makes only specified properties optional (alias pattern)
* Cleaner syntax alternative to Optional<T, K>
*/
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Usage: PartialBy<T, K>
interface Order {
id: string;
customerId: string;
items: string[];
total: number;
shippingAddress: string;
billingAddress: string;
notes: string;
}
type UpdateOrderPayload = PartialBy<Order, "notes" | "shippingAddress" | "billingAddress">;
// Result: { id: string; customerId: string; items: string[]; total: number; notes?: string; shippingAddress?: string; billingAddress?: string; }
const orderUpdate: UpdateOrderPayload = {
id: "ord-123",
customerId: "cust-456",
items: ["item-1", "item-2"],
total: 99.99
// notes, shippingAddress, billingAddress are optional
};
// ============================================
// Practical Combination Example
// ============================================
interface DatabaseEntity {
readonly id: string;
readonly createdAt: Date;
readonly updatedAt: Date;
name: string;
data: {
value: string | null;
metadata: {
tags: string[] | undefined;
} | null;
};
}
// Combine multiple mapped types for a create payload:
type CreateEntityPayload = Mutable<
Optional<
Nullable<Omit<DatabaseEntity, "id" | "createdAt" | "updatedAt">>,
"data"
>
>;
const createPayload: CreateEntityPayload = {
name: "New Entity"
// data is optional, and when provided, its nested values can be null
};
console.log("All mapped types compiled successfully!");
console.log("Mutable user:", mutableUser);
console.log("Nullable product:", partialProduct);
console.log("Draft article:", draft);
console.log("New user payload:", newUser);
console.log("Validated response:", validatedData);
console.log("Order update:", orderUpdate);
console.log("Create payload:", createPayload);Mapped types in TypeScript allow you to create new types by transforming properties of existing types. This collection demonstrates six essential utility types that solve common real-world typing challenges. The core mechanism uses the [P in keyof T] syntax to iterate over all properties of a type and apply transformations.
The Mutable<T> type uses the -readonly modifier syntax to remove readonly constraints. This is particularly useful when you receive immutable data from a library but need to modify it locally. The minus sign before readonly is a modifier removal operator - a powerful but often overlooked TypeScript feature. Similarly, Nullable<T> adds | null to every property, which is essential when working with APIs that return null for missing fields.
The Optional<T, K> and PartialBy<T, K> types demonstrate a key pattern: combining Omit, Pick, and Partial to achieve surgical precision in type transformation. Rather than making everything optional with Partial<T>, these types let you specify exactly which properties should be optional while keeping others required. This is invaluable for form handling, API payloads, and builder patterns. The RequireOnly<T, K> type inverts this pattern, making only specified keys required.
The DeepNonNullable<T> type showcases recursive type transformation, which is significantly more complex. It uses conditional types to check if a property is an object or array, then recursively applies the transformation. The -? modifier removes optional markers, while NonNullable<T[P]> removes null and undefined. This recursive approach handles nested objects of arbitrary depth, making it perfect for validating API responses or form data that has been fully populated.
These mapped types shine in large codebases where you need consistent type transformations across many interfaces. They reduce duplication and ensure type safety when working with different representations of the same data (API responses, form states, database entities). However, be cautious with deeply nested recursive types as they can impact TypeScript compilation performance. For most applications, keeping recursion to 3-4 levels deep is recommended.