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 matchU- Think of
Tas a full set of values,Uas the ones you want gone - Opposite of
Extract, which keeps only matching members - Implemented as
T extends U ? never : T - If all members of
TmatchU, the result isnever
Quick example
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:
type Exclude<T, U> = T extends U ? never : T;TypeScript applies this check to each union member separately. For Exclude<'a' | 'b' | 'c', 'a'>:
'a' extends 'a'→never'b' extends 'a'→'b''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.
| Utility | What it does |
|---|---|
Exclude<T, U> | Removes from T all members assignable to U |
Extract<T, U> | Keeps in T only members assignable to U |
type Mixed = string | number | boolean;
type OnlyStrings = Exclude<Mixed, number | boolean>; // string
type NumbersAndBools = Extract<Mixed, number | boolean>; // number | booleanSame input, opposite results.
When to use
- Strip
nullandundefinedfrom a union before working with values (thoughNonNullableis 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.
// 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.
type Roles = 'admin' | 'editor' | 'viewer';
// Typo: 'editer' matches nothing in the union
type Result = Exclude<Roles, 'editer'>; // 'admin' | 'editor' | 'viewer' - unchangedNo 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.
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 UserIf 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 | undefinedfrom 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
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 SafeMethodDefine the full set once, then carve out subsets where needed. No duplication.
Removing null from an API response type
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 readyA concise answer to help you respond confidently on this topic during an interview.