Exclude<UnionType, ExcludedMembers>
Removes from a union every member assignable to the second type, producing a narrower union.
type Exclude<UnionType, ExcludedMembers> = UnionType extends ExcludedMembers ? never : UnionType- Added in TypeScript 2.8
Parameters
| Parameter | Type | Description |
|---|---|---|
$UnionType | any type | The union to subtract from. Because it appears naked on the left of the conditional, the conditional distributes over each member individually. |
$ExcludedMembers | any type | Members 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 insideOmit. - 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
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.
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.
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