#59Hard

Get Optional

Build GetOptional<T>, which keeps only the optional fields of an object type. Filtering keys is half the job; each surviving property must also keep its ? modifier.

GetOptional<T> keeps every optional field of an object type and drops the rest. Picking the right keys is only half the job. The result must also keep the ? modifier on every surviving property, so bar?: string stays bar?: string. The solution combines the {} extends Pick<T, K> optionality probe with as-remapped key filtering, and homomorphic mapped types hand you the modifiers for free.

For example

[object Object]

Challenge Instructions: Get Optional

Hard

Implement the advanced util type GetOptional<T>, which remains all the optional fields

For example

[object Object]

View on GitHub: https://tsch.js.org/59

Change the following code to make the test cases pass (no type check errors).

Loading...

Detailed Explanation

The whole solution is one mapped type:

type GetOptional<T> = {
  [K in keyof T as {} extends Pick<T, K> ? K : never]: T[K]
}

It's the mirror image of GetRequired<T>: same probe, swapped branches.

The optionality probe

The ? modifier isn't visible in a property's value type, so the second test case defeats any undefined-based check:

type T = { foo: undefined; bar?: undefined }
// GetOptional<T> must be { bar?: undefined }

Instead, isolate each property with Pick, which preserves modifiers, and test assignability of the empty object:

// Pick<T, 'foo'> = { foo: undefined } → {} extends it? false (foo is mandatory)
// Pick<T, 'bar'> = { bar?: undefined } → {} extends it? true  (bar may be absent)

{} extends Pick<T, K> asks: is a value with no properties at all still a valid instance? Only optional properties allow that, so the check is true exactly for the optional keys.

Filtering in the key position

The as clause of a mapped type rewrites keys, and any key that evaluates to never is dropped from the result entirely. Our clause keeps optional keys and erases required ones:

[object Object]

Walking { foo: number; bar?: string }:

// K = 'foo': probe is false → key becomes never → dropped
// K = 'bar': probe is true  → key stays 'bar'   → kept with value string
// result: { bar?: string }

Where the ? comes from

The solution never writes a ?, yet the result has one. A mapped type over keyof T (even with an as clause) is homomorphic: TypeScript copies each source property's readonly and ? modifiers onto the corresponding output property. bar was optional in T, so it stays optional in the result.

This matters for correctness, not just cosmetics: Equal<{ bar?: string }, { bar: string | undefined }> is false. A non-homomorphic detour, such as mapping over a precomputed union of optional keys, loses the modifier:

type Wrong<T> = { [K in OptionalKeys<T>]: T[K] }
// Wrong<{ bar?: string }> = { bar: string | undefined }, modifier lost

The ? is gone and the tests fail. Keeping K in keyof T as the iteration source and filtering via as is what preserves the modifiers.

Edge cases the tests cover

Together with GetRequired, RequiredKeys, and OptionalKeys, you now have the full toolkit for slicing object types by optionality, all built from one assignability trick and one filtering pattern.

This challenge is originally from here.

Share this challenge

Learn the Concepts