Hone logo
Hone
Problems

TypeScript Interface Utility: DeepPartial<T>

This challenge focuses on creating a powerful TypeScript utility type. You'll build a DeepPartial<T> type that transforms a given interface or type into one where all properties, including nested ones, are optional. This is extremely useful when dealing with configuration objects or initial states where you might only want to provide a subset of properties.

Problem Description

Your task is to implement a TypeScript utility type called DeepPartial<T>. This type should take a generic type T as input and return a new type where all properties of T are made optional, recursively. This means that if T has nested objects or arrays, their properties should also be made optional.

Key Requirements:

  • The DeepPartial<T> type must recursively make all properties optional.
  • It should handle primitive types, objects, arrays, and union types correctly.
  • The utility type should be reusable and applicable to any given TypeScript interface or type.

Expected Behavior:

When DeepPartial<T> is applied to a type T, any property K of T should become K?: DeepPartial<T[K]>. This should apply to all levels of nesting within T.

Edge Cases to Consider:

  • Arrays: Properties that are arrays should become optional arrays where the elements of the array are also deeply partial.
  • Union Types: If a property is a union type (e.g., string | number), it should still be a union of partial types.
  • Null and Undefined: Handle null and undefined types appropriately within the recursion.
  • Functions: Functions should generally remain as they are (not made optional unless they are part of a larger optional structure).

Examples

Example 1:

interface User {
  id: number;
  name: string;
  address: {
    street: string;
    city: string;
    zipCode: number;
  };
  roles: string[];
}

type PartialUser = DeepPartial<User>;

// Expected type for PartialUser:
// {
//   id?: number | undefined;
//   name?: string | undefined;
//   address?: {
//     street?: string | undefined;
//     city?: string | undefined;
//     zipCode?: number | undefined;
//   } | undefined;
//   roles?: (string | undefined)[] | undefined;
// }

Example 2:

interface Settings {
  theme: 'dark' | 'light';
  notifications: {
    email: boolean;
    sms: boolean;
  };
  timeout: number | null;
}

type PartialSettings = DeepPartial<Settings>;

// Expected type for PartialSettings:
// {
//   theme?: 'dark' | 'light' | undefined;
//   notifications?: {
//     email?: boolean | undefined;
//     sms?: boolean | undefined;
//   } | undefined;
//   timeout?: number | null | undefined;
// }

Example 3: (Handling arrays of primitives and nested objects)

interface Product {
  name: string;
  tags: string[];
  variants: Array<{
    color: string;
    size: string;
    price: number;
  }>;
}

type PartialProduct = DeepPartial<Product>;

// Expected type for PartialProduct:
// {
//   name?: string | undefined;
//   tags?: (string | undefined)[] | undefined;
//   variants?: Array<{
//     color?: string | undefined;
//     size?: string | undefined;
//     price?: number | undefined;
//   }> | undefined;
// }

Constraints

  • Your solution must be implemented purely in TypeScript.
  • No external libraries or packages are allowed.
  • The utility type should be performant and not cause excessive compile-time overhead for typical use cases.

Notes

  • Consider how to handle recursive types. TypeScript's conditional types and mapped types will be your primary tools.
  • Pay close attention to the return type of recursive calls for properties that are arrays.
  • Think about the base cases for your recursion – when do you stop making things partial? Primitive types and null are good candidates.
Loading editor...
typescript