Build OptionalKeys<T>, which collects every optional key of an object type into a union. The hard part: optionality is a modifier, so checking for undefined gets you nowhere.
OptionalKeys<T> collects the names of all optional properties into a union. It's the mirror image of RequiredKeys<T>, and the difficulty is the same: optionality is a modifier on the property, not part of its value type. You can't detect it by looking for undefined. What works instead is an assignability trick that asks the compiler directly whether a property may be left out.
For example
type Result = OptionalKeys<{ a: number; b?: string }>
// expected to be 'b'Implement the advanced util type OptionalKeys<T>, which picks all the optional keys into a union.
View on GitHub: https://tsch.js.org/90
Change the following code to make the test cases pass (no type check errors).
Here is the complete solution:
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never
}[keyof T]Map every key to either its own name or never, then index into the result to union the survivors. A few pieces here deserve a closer look.
undefined failsThe second test case is designed to break naive solutions:
type T = { a: undefined; b?: undefined }
// OptionalKeys<T> must be 'b'Both properties have exactly the type undefined, yet only b is optional. A check like undefined extends T[K] would report both keys. Whether a property is optional lives in the ? modifier, so you need a test that reacts to the modifier itself.
PickPick<T, K> produces a single-property object type and preserves modifiers:
// Pick<T, 'a'> = { a: undefined }
// Pick<T, 'b'> = { b?: undefined }That preservation is what makes the solution possible. It lets us examine one property's modifier in isolation, without interference from its siblings.
Now check whether the empty object satisfies that one-property type:
{} extends { b?: undefined } → true. An optional property may be absent, so an object with nothing in it is a valid { b?: undefined }.{} extends { a: undefined } → false. A required property must exist, even when its type is undefined, and {} is missing it.So {} extends Pick<T, K> asks: can you construct this type without providing K? That is the definition of an optional key. Optional keys take the K branch, required keys take never.
-? and the final indexThe mapped type builds an intermediate object of verdicts. For { a: number; b?: string }:
[object Object]Two details make the last step work:
-? removes the copied optional modifier. Without it, b's slot would be 'b' | undefined when read back, and undefined would leak into your union.[keyof T] indexes with all keys at once, producing the union of all values: never | 'b'. Since never vanishes from unions, the result is just 'b'.{ a: undefined; b?: undefined } → 'b': modifiers, not value types, decide the outcome.{ a: undefined; b?: undefined; c?: string; d?: null } → 'b' | 'c' | 'd': every optional key contributes its name to the union.{} → never: with no keys to map, indexing by keyof {} (which is never) yields never for free.If you've already solved RequiredKeys<T>, compare the two solutions. They are identical except the conditional branches are swapped: one assignability trick, two utility types.
This challenge is originally from here.