Skip to content
TypeScript types · Core · about 15 minutes

Exclude and Omit, by hand

Write two types. MyExclude<T, U> removes from the union T every member that is assignable to U. MyOmit<T, K> is T without the keys K, keeping readonly and optional as they are. Do not use the built-in Exclude, Omit or Pick.

Start from

// Remove from the union T every member assignable to U.
type MyExclude<T, U> = any;

// T without the keys K.
type MyOmit<T, K> = any;

Not allowed here, so the type is written by hand: Exclude, Omit, Pick.

Hints

Each hint gives away a little more. Open one only when you need it.

  1. Hint 1

    A conditional type on a bare type parameter runs once for each member of a union: T extends U ? never : T.

  2. Hint 2

    For MyOmit, a mapped type over keyof T can drop a key by renaming it to never: [P in keyof T as P extends K ? never : P].

Solution

From here on the page gives the method away. If you are still solving, go back to the hints.

TypeScript
type MyExclude<T, U> = T extends U ? never : T;

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

The checks it must pass

type _1 = Expect<Equal<MyExclude<"a" | "b" | "c", "a">, "b" | "c">>;
type _2 = Expect<Equal<MyExclude<string | number | boolean, string | boolean>, number>>;
type _3 = Expect<Equal<MyOmit<{ id: number; name: string; done: boolean }, "done">, { id: number; name: string }>>;
type _4 = Expect<Equal<MyOmit<{ id: number; name: string; done: boolean }, "id" | "done">, { name: string }>>;
type _5 = Expect<Equal<MyOmit<{ readonly id: number; name?: string; done: boolean }, "done">, { readonly id: number; name?: string }>>;

What an interviewer looks for

It tests distributive conditional types and key remapping, the two ideas behind most utility types. Omit is used daily for component props.