Deep partial
Write DeepPartial<T>: every property is optional at every depth. An array stays an array, with DeepPartial applied to its items. Function types stay as they are. It is the type of a patch object, for example the body of a PATCH request or a test fixture override.
Start from
// Make every property optional, at every depth.
type DeepPartial<T> = any;Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1
Four cases, in this order: a function, an array (use infer for the item), an object (map it with ?), anything else.
Hint 2
The array case comes before the object case, because an array is also an object.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
TypeScript
type DeepPartial<T> = T extends (...args: any[]) => any
? T
: T extends (infer U)[]
? DeepPartial<U>[]
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;The checks it must pass
type _1 = Expect<Equal<DeepPartial<{ a: number; b: string }>, { a?: number; b?: string }>>;
type _2 = Expect<Equal<DeepPartial<{ a: { b: { c: number } } }>, { a?: { b?: { c?: number } } }>>;
type _3 = Expect<Equal<DeepPartial<{ list: { id: number }[] }>, { list?: { id?: number }[] }>>;
const patch: DeepPartial<{ user: { name: string; address: { city: string } } }> = { user: { address: {} } };
// @ts-expect-error
const wrongPatch: DeepPartial<{ user: { name: string } }> = { user: { name: 5 } };What an interviewer looks for
It combines recursion, infer and mapped modifiers. The order of the cases is the interview point: an array must be caught before the object case.