#89Hard

Required Keys

Build RequiredKeys<T>, which collects every required key of an object type into a union. Checking for undefined fails; you have to test the ? modifier itself.

Some object properties must be present and others may be left out, and the difference lives in the ? modifier rather than in the value types. RequiredKeys<T> collects the required ones into a union of key names. Checking for undefined doesn't work here, because { a: undefined } is required while { b?: undefined } is optional. The technique that does work also powers OptionalKeys, GetRequired, and several other utility types.

For example

type Result = RequiredKeys<{ foo: number; bar?: string }>;
// expected to be 'foo'

Challenge Instructions: Required Keys

Hard

Implement the advanced util type RequiredKeys<T>, which picks all the required keys into a union.

For example

type Result = RequiredKeys<{ foo: number; bar?: string }>;
// expected to be "foo"

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

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

Loading...

Detailed Explanation

The complete type:

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

Three lines, holding two of TypeScript's most useful idioms.

Why the obvious approach fails

Your first instinct might be: 'a key is optional if undefined is part of its type.' But look at the second test case:

type T = { a: undefined; b?: undefined }
// RequiredKeys<T> must be 'a'

Both properties have the type undefined, yet a is required and b is optional. Optionality is a modifier on the property, not part of the value type, so you need a check that observes the ? marker directly.

The {} extends Pick<T, K> trick

Pick<T, K> builds a one-property object type for each key. For our example:

// Pick<T, 'a'> = { a: undefined }
// Pick<T, 'b'> = { b?: undefined }

Now ask: is the empty object {} assignable to that type?

So {} extends Pick<T, K> is true exactly when K is optional. Since we want the required keys, the conditional is flipped: optional keys map to never, required keys map to themselves (K).

Mapping keys to keys

The mapped type doesn't build the final answer directly. It builds an intermediate object whose values are the verdicts:

// for T = { a: number; b?: string }:
// { a: 'a'; b: never }

Each required key stores its own name; each optional key stores never.

The -? modifier

Mapped types over keyof T are homomorphic: they copy the ? modifier from the source. Without intervention, b would stay optional in the intermediate object, and reading its value would give you never | undefined = undefined, which would pollute the result. -? strips the optional modifier from every property, so the intermediate object is exactly { a: 'a'; b: never }.

Indexing with [keyof T]

Finally, [keyof T] looks up all the values at once and unions them:

[object Object]

never disappears from unions, so only the required key names survive. This 'map keys to K or never, then index' pattern is the standard way to filter keys in TypeScript, and you'll reuse it constantly.

Edge cases the tests cover

Once you own this pattern, OptionalKeys<T> is the same type with the conditional branches swapped.

This challenge is originally from here.

Share this challenge

Learn the Concepts