Skip to the content.

Omit in TypeScript isn’t distributive

Imagine you have this type X.

type X = {
  readonly x: string;
} & (
  | {
    readonly type: 'Y';
    readonly y: number;
  }
  | {
    readonly type: 'Z';
    readonly z: boolean;
  }
);

And you want to define a type similar to X but without property x. You might think you can get it like this.

type X2 = Omit<X, 'x'>;

But when you try to use it, you’ll find that the complier complains.

const o: X2 = {
  type: 'Y',
  y: 1,
};

Why? When you expand X2, you’ll find it’s like this. As you can see, it doesn’t have neither y nor z.

type X2 = {
  readonly type: 'Y' | 'Z';
};

This is because Omit isn’t distributive, and only gets the common properties of all the union types.

As you may have known, keyof X isn’t 'x' | 'type' | 'y' | 'z', but 'x' | 'type'. Omit<X, 'x'> has Exclude<keyof X, 'x'> as keys. So it’ll only have type property.

You can define a distributive version of Omit like this.

type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;

DistributiveOmit<X, 'x'> will be expanded to a union of each type without x.

DistributedOmit<X, 'x'> =
  | Omit<{
      readonly x: string;
    } & {
      readonly type: 'Y';
      readonly y: number;
    }>
  | Omit<{
      readonly x: string;
    } & {
      readonly type: 'Z';
      readonly z: boolean;
    }>;

So this definition is valid.

const o: DistributiveOmit<X, 'x'> = {
  type: 'Y',
  y: 1,
};

Note that you can have a distributive version of keyof this way.

type DistributiveKeyOf<t> = T extends unknown ? keyof T : never;

then, DistributiveKeyOf<X> will be expanded to 'x' | 'type' | 'y' | 'z'.