Exclude, Extract & NonNullable

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.

Code examples

Exclude for Removing Union Members

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.

Extract for Filtering Union Members

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.

NonNullable for Stripping Nullish Types

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.

Key points

Concepts covered

Exclude<T,U>, Extract<T,U>, NonNullable<T>, conditional type filtering, union manipulation