← Back to snippets
Optional<T> Recursive Type
A TypeScript utility type that makes all properties optional, including nested objects
typescript
intermediate
A TypeScript utility type that makes all properties in an object optional, including those in nested objects. Perfect for partial updates, form data, and configuration objects.
Snippet
type Optional<T> = {
[K in keyof T]?: T[K] extends Record<string, unknown> ? Optional<T[K]> : T[K];
};
Usage Example
// Original type with required properties
type User = {
id: number;
name: string;
address: {
street: string;
city: string;
zipCode: string;
};
preferences: {
theme: {
color: string;
mode: 'light' | 'dark';
};
notifications: boolean;
};
};
// All properties are optional, including nested ones
type PartialUser = Optional<User>;
// ✅ Valid - can omit any property at any level
const userUpdate: PartialUser = {
name: "Jane Doe",
address: {
// Can omit city and zipCode
street: "123 Main St"
},
// Can omit preferences entirely
};
How It Works
// The magic happens through conditional typing
type Optional<T> = {
// [K in keyof T]? - Makes each property optional with the ? modifier
// For each property check if its value is a plain object (Record<string, unknown>):
// - If it is, recursively apply Optional to that object
// - If not, keep the original type
[K in keyof T]?: T[K] extends Record<string, unknown> ? Optional<T[K]> : T[K];
};
Why Record<string, unknown>
instead of object
Using Record<string, unknown>
instead of object
provides more precise type checking:
- More specific object detection:
object
includes arrays, functions, and other non-primitive typesRecord<string, unknown>
specifically targets plain objects with string keys
- Better handling of special types:
- Arrays remain intact instead of being recursively processed
- Function properties are preserved as-is
- Only actual object structures are made recursively optional
Real World Example
// API update payload with deeply nested optional fields
type UserProfileSettings = {
personalInfo: {
displayName: string;
bio: string;
birthDate: Date;
};
securitySettings: {
twoFactor: {
enabled: boolean;
method: 'sms' | 'app';
};
passwordReset: boolean;
};
notifications: {
email: boolean;
push: boolean;
marketing: boolean;
};
favoriteNumbers: number[]; // Array property
calculateAge: () => number; // Function property
};
// Function to update user settings, accepting partial data at any level
function updateUserSettings(userId: string, settings: Optional<UserProfileSettings>) {
// Only specified fields will be updated
console.log(`Updating settings for user ${userId}`);
// This is safe - we don't need to check if every property exists
return apiClient.patch(`/users/${userId}/settings`, settings);
}
// Usage
updateUserSettings('user123', {
personalInfo: {
// Only update bio, leave other fields unchanged
bio: "TypeScript enthusiast"
},
// Arrays are preserved as-is (not recursively made optional)
favoriteNumbers: [42, 7, 13],
// Only update email notification setting
notifications: {
email: false
}
});
Improved Version for Arrays
If you want to handle arrays of objects specially, you can extend the type:
// Enhanced version that handles arrays of objects specially
type DeepOptional<T> = {
[K in keyof T]?: T[K] extends Record<string, unknown>
? DeepOptional<T[K]>
: T[K] extends Array<infer U>
? U extends Record<string, unknown>
? Array<DeepOptional<U>>
: T[K]
: T[K];
};
// Usage with arrays of objects
type TodoList = {
title: string;
items: Array<{
task: string;
completed: boolean;
subtasks: { description: string; done: boolean }[];
}>;
settings: {
color: string;
priority: number;
};
};
// Can update any part of the structure, including nested objects in arrays
const update: DeepOptional<TodoList> = {
items: [
{
task: "Learn TypeScript",
// completed can be omitted
subtasks: [{ description: "Study utility types" }]
}
],
settings: { color: "blue" }
};
Comparison to Standard Partial<T>
TypeScript's built-in Partial<T>
only makes the top level properties optional:
// Built-in TypeScript utility
type StandardPartial = Partial<User>;
// ❌ Error - nested properties are still required
const invalid: StandardPartial = {
address: {} // Error: missing required properties
};
// ✓ Works correctly with our recursive Optional
const valid: Optional<User> = {
address: {} // All nested properties are optional
};
TypeScript Version Requirements
This utility type works in TypeScript 2.8 and above. The recursion pattern became more reliable in TypeScript 3.7+.