Difficulty: Advanced
`Exclude<T, U>` removes from union type `T` all members that are assignable to `U`. It is defined as the distributive conditional type `T extends U ? never : T`. This is useful when you need to create a narrower union by removing specific members. For example, `Exclude<'a' | 'b' | 'c', 'a'>` produces `'b' | 'c'`.
`Extract<T, U>` is the opposite of `Exclude` - it keeps only the members of `T` that are assignable to `U`. Its definition is `T extends U ? T : never`. This is useful for filtering a union to only the members that match a certain pattern. For example, `Extract<string | number | boolean, string | number>` produces `string | number`.
`NonNullable<T>` removes `null` and `undefined` from a type. It is equivalent to `Exclude<T, null | undefined>`. This utility is extremely common in practice because TypeScript's strict null checks mean many values are typed as `T | null` or `T | undefined`. NonNullable strips away the nullable parts when you have already validated that a value exists.
These three utilities work on union types by filtering their members. They are the type-level equivalents of array filtering - just as `array.filter()` removes elements that don't match a predicate, these utilities remove type members that don't match a type condition. They are building blocks for more complex type transformations and appear frequently in library type definitions.
type AllEvents = "click" | "hover" | "scroll" | "keydown" | "keyup";
type MouseEvents = Exclude<AllEvents, "keydown" | "keyup">;
// "click" | "hover" | "scroll"
type NonStringPrimitives = Exclude<string | number | boolean | symbol, string>;
// number | boolean | symbol
function handleMouseEvent(event: MouseEvents): string {
return `Mouse event: ${event}`;
}
console.log(handleMouseEvent("click"));
console.log(handleMouseEvent("hover"));
console.log(handleMouseEvent("scroll"));
Exclude removes 'keydown' and 'keyup' from the AllEvents union, leaving only mouse-related events. TypeScript enforces that only the remaining members can be passed.
type AllTypes = string | number | boolean | null | undefined | object;
type Primitives = Extract<AllTypes, string | number | boolean>;
// string | number | boolean
type EventMap = {
click: { x: number; y: number };
keydown: { key: string };
scroll: { position: number };
hover: { target: string };
};
type PositionEvents = Extract<keyof EventMap, "click" | "scroll">;
// "click" | "scroll"
function getPosition(event: PositionEvents): string {
return `Position event: ${event}`;
}
console.log(getPosition("click"));
console.log(getPosition("scroll"));
Extract keeps only the members of the first union that are assignable to the second. It acts as a filter, retaining only matching members.
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
interface ApiResponse {
data: string | null;
error: string | null;
metadata: { timestamp: number } | undefined;
}
function processData(response: ApiResponse): string {
const data: NonNullable<ApiResponse["data"]> | "empty" =
response.data ?? "empty";
return `Data: ${data}`;
}
console.log(processData({ data: "hello", error: null, metadata: undefined }));
console.log(processData({ data: null, error: "fail", metadata: undefined }));
NonNullable strips null and undefined from the type. Combined with the nullish coalescing operator (??), it provides both type safety and a runtime fallback.
Exclude<T,U>, Extract<T,U>, NonNullable<T>, conditional type filtering, union manipulation