Challenge 2

The instructions for this challenge are:

Implement the built-in Omit<T, K> generic without using it.

Constructs a type by picking all properties from T and then removing K

Example code that should compile correctly is provided:

interface Todo {
    title: string
    description: string
    completed: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
    completed: false,
}

We start with the type:

type MyOmit<T, K> = any

From the example we know that K is going to represent keys from T. We can do this with keyof to change our type to:

type MyOmit<T, K extends keyof T> = any

We know that the eventual type will be an object. We also know that the object will only have keys from T so we will want to use keyof again on the right-hand side of the expression. As we know the type of the keys of the resulting object we will be able to use an index signature:

type MyOmit<T, K extends keyof T> = { [P in keyof T]: any }

The in keyword here is used to constrain the index signature to only accept a key of T. This is a Mapped Type. There are still two problems we need to solve:

  1. We aren’t omitting the values we are supposed to.
  2. We don’t maintain the types of the values in the object (can affect completion in editors).

The second issue is trivial to fix:

type MyOmit<T, K extends keyof T> = { [P in keyof T]: T[P] }

This change should be fairly readable if you have a Javascript background. In Typescript it’s referred to as value type and will allow you to customise the emitted value a little if you wish (we aren’t doing that here though).

The first issue requires us to reuse a technique from the first challenge: conditional types. We can test whether our mapped type is in the omit specification K. If it is then we can assign it to the empty set never (in a mapped type never will filter the key out), if it is not then we simply use the mapped type (P). This removes any keys in K from the emitted type, implementing the Omit type.

This gives us the eventual solution:

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

Blog

Miscellaneous thoughts.