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'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).
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.
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.
{} extends Pick<T, K> trickPick<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?
{} extends { b?: undefined } is true: every property is optional, so an object with no properties satisfies it.{} extends { a: undefined } is false: a must be present, and {} doesn't have it.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).
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.
-? modifierMapped 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 }.
[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.
{ a: undefined; b?: undefined } → 'a': the whole reason for the Pick trick. Value types are identical; only the modifier differs.{ a: undefined; b?: undefined; c: string; d: null } → 'a' | 'c' | 'd': multiple survivors union together naturally.{} → never: keyof {} is never, so the index produces never without any special-casing.Once you own this pattern, OptionalKeys<T> is the same type with the conditional branches swapped.
This challenge is originally from here.