Exclude, Extract & NonNullable
Exclude, Extract, and NonNullable operate on union types rather than object types. They use distributive conditional types to filter union members in or out: Exclude removes members, Extract keeps matching members, and NonNullable strips null and undefined. They are essential for refining unions you derive from other types — for example, narrowing a set of string literals or cleaning up a T | null | undefined.
Exclude: remove matching members
Exclude<T, U> returns the members of union T that are not assignable to U. Think of it as set subtraction on unions.
type Status = "idle" | "loading" | "success" | "error";
type Settled = Exclude<Status, "idle" | "loading">;
// "success" | "error"
type WithoutNumbers = Exclude<string | number | boolean, number | boolean>;
// string
Exclude underpins Omit, which removes object keys by excluding them from keyof T. Whenever you need “this union, minus those members”, reach for Exclude.
Extract: keep matching members
Extract<T, U> is the complement: it returns the members of T that are assignable to U — the intersection of the two unions, member by member.
type Mixed = string | number | (() => void) | boolean;
type OnlyPrimitives = Extract<Mixed, string | number | boolean>;
// string | number | boolean
type OnlyCallable = Extract<Mixed, Function>;
// () => void
A common use is pulling specific variants out of a discriminated union by their discriminant:
type Event =
| { type: "click"; x: number; y: number }
| { type: "key"; key: string }
| { type: "scroll"; delta: number };
type KeyEvent = Extract<Event, { type: "key" }>;
// { type: "key"; key: string }
NonNullable: strip null and undefined
NonNullable<T> removes both null and undefined from a type. It is the type-level equivalent of a non-null assertion, but safe and declarative.
type MaybeName = string | null | undefined;
type Name = NonNullable<MaybeName>; // string
function greet(name: NonNullable<MaybeName>) {
return `Hi, ${name.toUpperCase()}`; // no null check needed
}
This is handy when a value is known to be present after a guard, or when modeling the result of a filter that drops nullish entries.
How they are built
All three are short conditional types that distribute over unions:
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
type MyNonNullable<T> = T & {}; // modern definition; previously Exclude<T, null | undefined>
For Exclude/Extract, the conditional is applied to each union member independently. Members that resolve to never vanish from the resulting union, because never is the identity element of unions. NonNullable<T> now uses the T & {} trick, since intersecting with the empty object type removes null and undefined.
Quick comparison
| Utility | Keeps members that… | Result for "a" | "b", U = "a" |
|---|---|---|
Exclude<T, U> | are NOT assignable to U | "b" |
Extract<T, U> | ARE assignable to U | "a" |
NonNullable<T> | are not null/undefined | n/a (single-purpose) |
These utilities only filter union members. On a non-union type,
Exclude<string, number>is juststring— there is nothing to subtract. Distribution requires a naked type parameter over a union.
A practical combination
Combine them to derive precise types from real data shapes:
interface Form {
name: string;
age: number | null;
bio: string | undefined;
}
// keys whose value can be missing
type NullableKeys = {
[K in keyof Form]: null extends Form[K] ? K : never;
}[keyof Form]; // "age"
Here Extract/Exclude thinking drives a key-filtering mapped type, a common pattern for building typed validators.
Best Practices
- Use
Exclude/Extractto refine literal-union types instead of re-typing them by hand. - Reach for
Extract<Union, { type: "x" }>to pull a single variant from a discriminated union. - Prefer
NonNullable<T>over the!non-null assertion for declarative, safe nullish removal. - Remember these distribute over unions only — they have no effect on non-union inputs.
- Combine with
keyofand mapped types to filter object keys by their value characteristics. - Keep
Uminimal and explicit so the subtraction/intersection intent is obvious to readers.