#90Hard

Optional Keys

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'

Challenge Instructions: Optional Keys

Hard

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).

Loading...

Detailed Explanation

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.

Why checking for undefined fails

The 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.

Isolating one property with Pick

Pick<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.

Asking the assignability question

Now check whether the empty object satisfies that one-property type:

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 index

The mapped type builds an intermediate object of verdicts. For { a: number; b?: string }:

[object Object]

Two details make the last step work:

Edge cases the tests cover

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.

Share this challenge

Learn the Concepts