Skip to main content

Utility type exclude in TypeScript

Exclude<T, U> is a TypeScript utility type that removes from a union all members assignable to U, leaving only what does not match.

Theory

TL;DR

  • Exclude<T, U> filters a union by removing members that match U
  • Think of T as a full set of values, U as the ones you want gone
  • Opposite of Extract, which keeps only matching members
  • Implemented as T extends U ? never : T
  • If all members of T match U, the result is never

Quick example

ts
type Status = 'active' | 'inactive' | 'banned' | 'deleted'; // Remove statuses you don't need in this context type VisibleStatus = Exclude<Status, 'banned' | 'deleted'>; // Result: 'active' | 'inactive'

Two members are gone. The rest survive.

How the compiler handles this

Exclude is defined in TypeScript's standard library as a distributive conditional type:

ts
type Exclude<T, U> = T extends U ? never : T;

TypeScript applies this check to each union member separately. For Exclude<'a' | 'b' | 'c', 'a'>:

  1. 'a' extends 'a'never
  2. 'b' extends 'a''b'
  3. 'c' extends 'a''c'

The results merge: never | 'b' | 'c', which simplifies to 'b' | 'c'. TypeScript drops never from unions automatically.

Exclude vs Extract

These two are mirrors of each other.

UtilityWhat it does
Exclude<T, U>Removes from T all members assignable to U
Extract<T, U>Keeps in T only members assignable to U
ts
type Mixed = string | number | boolean; type OnlyStrings = Exclude<Mixed, number | boolean>; // string type NumbersAndBools = Extract<Mixed, number | boolean>; // number | boolean

Same input, opposite results.

When to use

  • Strip null and undefined from a union before working with values (though NonNullable is shorter for this specific case)
  • Narrow API response types before passing them to a handler function
  • Filter string literal unions: event names, action types, status codes
  • Build a subset of a large role or status union for a specific context

Common mistakes

Confusing Exclude with Omit. They sound similar but target different things. Omit removes keys from an object type. Exclude removes members from a union.

ts
// Wrong: won't remove a property from an object type type WithoutKind = Exclude<{ kind: 'circle'; size: number }, 'kind'>; // Still { kind: 'circle'; size: number } - nothing changed // Right: use Omit for object keys type WithoutKind2 = Omit<{ kind: 'circle'; size: number }, 'kind'>; // { size: number }

Typos in string literals. If the value in U does not match anything in T, nothing gets removed and TypeScript gives no error.

ts
type Roles = 'admin' | 'editor' | 'viewer'; // Typo: 'editer' matches nothing in the union type Result = Exclude<Roles, 'editer'>; // 'admin' | 'editor' | 'viewer' - unchanged

No warning, no error. I've seen this eat 20 minutes of debugging in real codebases. Always double-check spelling in U.

Expecting Exclude to filter by object properties. It distributes over union members, not over fields inside a single object.

ts
type User = { role: 'admin' | 'viewer' }; // Won't filter by .role - the whole User object is the union member type AdminOnly = Exclude<User, { role: 'viewer' }>; // Still User

If you need to filter object unions this way, each variant must be a separate union member, not a property inside one type.

Real-world usage

  • Redux/Zustand: exclude 'INIT' or 'RESET' from the action union when writing specific slice handlers
  • React props: narrow a string literal prop in a child component without redefining the full type
  • Form state: Exclude<FieldStatus, 'untouched'> to type only fields the user has interacted with
  • API client: strip null | undefined from a response union before passing data to a parser

Follow-up questions

Q: What is the internal definition of Exclude in TypeScript?
A: type Exclude<T, U> = T extends U ? never : T. It is a distributive conditional type, so TypeScript applies the check per union member, not to the union as a whole.

Q: What is the difference between Exclude and Omit?
A: Exclude works on union members, Omit works on object keys. Exclude<'a' | 'b', 'a'> gives 'b'. Omit<{ a: 1; b: 2 }, 'a'> gives { b: 2 }.

Q: What does Exclude<string, string> return?
A: never. Every member of T is assignable to U, so all members map to never. An empty union collapses to never in TypeScript.

Q: What happens to distribution if T is wrapped in a tuple?
A: Distribution stops. [T] extends [U] checks the tuple as a whole, not each union member separately. This is a common edge case when writing advanced generic utilities that intentionally suppress distribution.

Examples

Filtering HTTP methods

ts
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS'; // Keep only read-only methods type SafeMethod = Exclude<HttpMethod, 'POST' | 'PUT' | 'DELETE' | 'PATCH'>; // 'GET' | 'OPTIONS' function readOnly(method: SafeMethod, url: string) { return fetch(url, { method }); } readOnly('GET', '/api/users'); // ok readOnly('POST', '/api/users'); // TS error: 'POST' is not assignable to SafeMethod

Define the full set once, then carve out subsets where needed. No duplication.

Removing null from an API response type

ts
type ApiStatus = 'ok' | 'error' | 'pending' | null | undefined; // After the response resolves, null and undefined are no longer possible type ResolvedStatus = Exclude<ApiStatus, null | undefined>; // 'ok' | 'error' | 'pending' function handleResolved(status: ResolvedStatus) { if (status === 'ok') { console.log('Request succeeded'); } }

NonNullable<ApiStatus> produces the same result here. Both are correct. NonNullable is a named shortcut for this specific pattern.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?