#34857Medium

Defined Partial Record

### Defined Partial Record Master advanced TypeScript type manipulation in this medium-level challenge on TypeScriptPro.

In this medium-level challenge, you'll implement DefinedPartial<T> which creates a union of all non-empty subsets of an object type, so that present keys have defined (non-undefined) values and absent keys simply do not exist on the type.

Challenge Instructions: Defined Partial Record

Medium

Defined Partial Record

Using a Record with union types as keys doesn't allow you to make an object with only some of them

const record: Record<'a' | 'b' | 'c', number> = { a: 42, b: 10 }
// error: Property 'c' is missing in type '{ a: number; b: number; }'
// but required in type 'Record<"a" | "b" | "c", number>'

Using a Partial Record with union types as keys allows you to make an object without all union members, but makes all keys and values optional, potentially leaving them undefined

const partial: Partial<Record<'a' | 'b' | 'c', number>> = { a: 42 }
const partialType = typeof partial // { a?: number | undefined, b? : number | undefined, c? : number | undefined }
const operation = 0 + partial.a // error: 'partial.a' is possibly 'undefined'
const access = partial.c // possible, type doesn't know that there is no such key

You need to make a type that takes the best of both worlds, creates all combinations of all the types in the union, so using a key that exists in the object gives you a defined type, but using a key that exists in the union and not in the object throws an error

const best: DefinedPartial<Record<'a' | 'b' | 'c', number>> = { a: 42 }
const sum = 0 + best.a // 42
const error = best.b // error: property 'b' does not exist on type '{ a: number; }'

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

ChallengeSolution
type A1 = Record<'a' | 'b', string>
type E1 = { a: string } | { b: string } | { a: string; b: string }
type D1 = DefinedPartial<A1>
type C1 = Expect<Equal<D1, E1>>

type A2 = Record<'a' | 'b' | 'c', string>
type E2 =
  | { a: string }
  | { b: string }
  | { c: string }
  | { a: string; b: string }
  | { a: string; c: string }
  | { b: string; c: string }
  | { a: string; b: string; c: string }
type D2 = DefinedPartial<A2>
type C2 = Expect<Equal<D2, E2>>

type A3 = Record<'a', number>
type E3 = { a: number }
type D3 = DefinedPartial<A3>
type C3 = Expect<Equal<D3, E3>>

type A4 = Record<'a', nu

Pro Challenge

Unlock 102+ medium, hard, and extreme challenges to master advanced TypeScript.

One-time payment. Lifetime access.

Detailed Explanation

type DefinedPartial<T, K extends keyof T = keyof T> =
  [K] extends [never]
    ? never
    : K extends keyof T
      ? { [P in K]: T[P] } | DefinedPartial<T, Exclude<keyof T, K>>
        | { [P in K]: T[P] } & DefinedPartial<T, Exclude<keyof T, K>> extends infer U
          ? U extends object ? { [P in keyof U]: U[P] } : never
          : never
      : never

A cleaner recursive approach that generates the powerset of keys:

type Powerset<K extends PropertyKey> = K extends K
  ? { [P in K]: unknown } | (Powerset<Exclude<K, K>> extends never ? never : never)
  : never
 
type DefinedPartial<T, K extends keyof T = keyof T> =
  K extends keyof T
    ? Pick<T, K> | Merge<Pick<T, K> & DefinedPartial<T, Exclude<keyof T, K>>>
    : never
 
type Merge<T> = { [K in keyof T]: T[K] }

The most straightforward correct solution distributes over each key and builds up combinations:

type DefinedPartial<T, K extends keyof T = keyof T> =
  K extends keyof T
    ? Pick<T, K> | (
        DefinedPartial<T, Exclude<keyof T, K>> extends infer Rest
          ? Rest extends any
            ? { [P in keyof (Pick<T, K> & Rest)]: (Pick<T, K> & Rest)[P] }
            : never
          : never
      )
    : never

How it works:

This challenge helps you understand powerset generation at the type level using distributive conditional types and how to apply this concept in real-world scenarios.

This challenge is originally from here.

Share this challenge

Learn the Concepts