Exclude<UnionType, ExcludedMembers>

Removes from a union every member assignable to the second type, producing a narrower union.

Signature
type Exclude<UnionType, ExcludedMembers> = UnionType extends ExcludedMembers ? never : UnionType
  • Added in TypeScript 2.8

Parameters

ParameterTypeDescription
$UnionTypeany typeThe union to subtract from. Because it appears naked on the left of the conditional, the conditional distributes over each member individually.
$ExcludedMembersany typeMembers assignable to this type are removed. Assignability, not equality, decides — so a supertype removes all of its subtypes.

Return value

The union of members of UnionType not assignable to ExcludedMembersA union containing each member of UnionType that is not assignable to ExcludedMembers; `never` if all of them are.

Errors

Never errors. An over-broad second argument silently yields `never`, and the error appears wherever that `never` is later used.

Changelog

  • PHP 3.5Became load-bearing for the standard library: `Omit<T, K>` is defined as `Pick<T, Exclude<keyof T, K>>`.

Exclude is a distributive conditional type: UnionType extends ExcludedMembers ? never : UnionType. Because UnionType is a bare type parameter, the conditional is applied to each union member separately and the results are unioned back together — which is what makes it read as union subtraction.

When to reach for it

  • Narrowing a literal union: allowed HTTP verbs, permitted statuses, a subset of theme names.
  • Computing key sets: Exclude<keyof T, "id"> is the engine inside Omit.
  • Removing a variant from a discriminated union by matching on its tag: Exclude<Shape, { kind: "circle" }>.

What trips people up

It filters by assignability, not by identity. Exclude<string | number | (() => void), Function> removes the function type because it is assignable to Function. Passing a supertype removes everything under it, sometimes far more than intended.

It cannot subtract from a non-union. Exclude<string, "a"> is string, not "every string but a" — string is a single member, and it is not assignable to "a", so nothing is removed. There is no negated type in TypeScript.

Excluding everything gives never, quietly. The declaration compiles; the confusing error lands at the first assignment.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Define a read-only subset of an action union that a public API is allowed to dispatch.
  • Compute the remaining keys of an interface after removing a few, the way Omit does.
  • Strip one variant out of a discriminated union so an exhaustive switch has fewer cases.

Frequently asked questions

How do I remove a member from a union type?

Use `Exclude<Union, Member>` — it distributes over the union and drops every member assignable to the second argument. For string literal unions this is exact union subtraction.

Why does Exclude return never?

Because every member of the first union was assignable to the second, usually because the second argument is broader than intended — `Exclude<"a" | "b", string>` is `never` since both literals are strings. The declaration itself compiles; the error appears at the first assignment to the resulting type.

What is the difference between Exclude and Omit?

`Exclude` subtracts members from a union of types, while `Omit` removes properties from an object type. `Omit` is built on `Exclude`: it is `Pick<T, Exclude<keyof T, K>>`.

More examples

Removing a variant from a discriminated union

Prints `square -> 4` and `triangle -> 3`. The switch has no `default` and no circle case, yet it compiles as exhaustive: `Exclude` matched the circle object against `{ kind: "circle" }` — assignability, so the extra `radius` property does not prevent the match — and removed it, leaving a two-member union the compiler can prove is fully handled.

Example · typescript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; x: number }
  | { kind: "triangle"; x: number; y: number };

// Everything except the circle variant
type Polygon = Exclude<Shape, { kind: "circle" }>;

function corners(shape: Polygon): number {
  switch (shape.kind) {
    case "square":
      return 4;
    case "triangle":
      return 3;
  }
}

const shapes: Polygon[] = [
  { kind: "square", x: 2 },
  { kind: "triangle", x: 3, y: 4 },
];

for (const shape of shapes) {
  console.log(shape.kind, "->", corners(shape));
}

Assignability cuts wider than you expect

Logs `[ 'hello', 42 ]` and then `a`. The three type aliases show the rule in each direction: `Function` removed the arrow type because functions are assignable to it; `string` removed both literals because literals are assignable to their primitive; and `"a"` removed nothing from `string`, because `string` as a whole is not assignable to `"a"`. The commented line is where a `never` result actually reports itself.

Example · typescript
type Mixed = string | number | (() => void);

type NoFunctions = Exclude<Mixed, Function>;  // string | number
type Nothing = Exclude<"a" | "b", string>;   // never — both are strings!
type Unchanged = Exclude<string, "a">;       // string — nothing to subtract

const values: NoFunctions[] = ["hello", 42];
console.log(values);

// const impossible: Nothing = "a";
//   -> Type 'string' is not assignable to type 'never'.

const anyString: Unchanged = "a";
console.log(anyString);

Discussion

  • Be the first to comment on this lesson.