Skip to main content

Utility type extract in TypeScript

Extract<T, U> picks from a union type T only the members assignable to U, returning a narrower union of matching types.

Theory

TL;DR

  • Think of a factory conveyor belt: drop in a mixed crate of parts (union T), set the filter shape (U), and only matching parts come out.
  • Extract checks assignability, not exact equality. A member passes if it structurally fits U.
  • Returns never when nothing matches. Account for that in function signatures.
  • Use it when a union is too broad for a specific branch and you need a type-safe subset.
  • Opposite of Exclude<T, U>.

Quick example

ts
type Status = "success" | "error" | "pending" | 200; type StringStatuses = Extract<Status, string>; // Result: "success" | "error" | "pending" // 200 is a number, not assignable to string, so it is filtered out const code: StringStatuses = "error"; // OK const num: StringStatuses = 200; // Type error

Extract walked through each member of Status and kept only those assignable to string. The number 200 did not pass.

Key difference

Extract tests assignability, not exact equality. So Extract<"success" | "error", string> returns "success" | "error" because both string literals are assignable to string. This also works for objects: if a union member has all the properties U requires, plus extras, it still passes through.

When to use

  • API response union too wide: extract the error or success subtype for a dedicated handler.
  • Role-based access: pull admin-level roles from a full roles union.
  • Discriminated unions: extract variants with a specific discriminant value.
  • Event filtering: keep only the event types your handler actually covers.

How the compiler handles this

TypeScript expands Extract<T, U> as T extends U ? T : never, applied to each union member separately. For each constituent, it checks structural assignability. No runtime cost - the type is erased at emit. This behavior is available since TypeScript 2.8, when conditional types were introduced.

Common mistakes

1. Expecting partial string matching

ts
type Status = "error" | "ERR_404"; type Want = Extract<Status, "error">; // "error" only. "ERR_404" is not assignable to the literal "error".

These are different literal types. Assignability between literals is exact. If you need both, use Extract<Status, "error" | "ERR_404">.

2. Nested object mismatch

ts
type Deep = { a: "x" } | { a: { b: "y" } }; type Fail = Extract<Deep, { a: string }>; // never! { b: "y" } is not assignable to string

TypeScript checks the full structure. A nested object is not a string. If you need to drill into a property, use Extract<Deep["a"], string>.

3. Ignoring never results

ts
type None = Extract<"a" | "b", number>; // never function handle(x: None) {} // Uncallable - TypeScript errors at every call site

When nothing matches, you get never. A variable typed as never cannot be assigned anything. Check your filter type before using the result in a function signature.

Real-world usage

From production React codebases, Extract shows up most often around API response types and role checks. A few concrete spots:

  • React Query: Extract<UseQueryResult, { data: T }> narrows loading vs success states.
  • Zod: pull specific schema validators from a union of z.ZodTypeAny.
  • tRPC: filter router procedures by input shape.
  • Role guards: Extract<AppRole, "admin" | "superadmin"> for protected routes.

Follow-up questions

Q: What does Extract<'a' | 'b', string> return?
A: 'a' | 'b'. Both string literals are assignable to string, so both pass through unchanged.

Q: How is Extract<T, U> different from T & U?
A: & creates an intersection of properties and can produce never on primitive conflicts. Extract selects whole union members that fit U, keeping their original shape intact.

Q: Write Extract<'a' | 1, string | number> manually.
A: ('a' extends string | number ? 'a' : never) | (1 extends string | number ? 1 : never) equals 'a' | 1. Both members pass.

Q: Why is Extract<{x: 1}, {x: string}> equal to never?
A: Because 1 is not assignable to string. The structural check fails on the x property.

Q: (Senior) How would you implement the inverse of Extract?
A: Exclude<T, U> is the built-in inverse. Custom implementation: T extends U ? never : T.

Examples

Basic: filtering a mixed union

ts
type Event = "click" | "keydown" | "focus" | "drag"; type KeyboardEvents = Extract<Event, "keydown" | "focus">; // "keydown" | "focus" function handleKeyboard(event: KeyboardEvents) { // TypeScript knows event is only "keydown" or "focus" console.log("Keyboard event:", event); }

Two string literals pass the filter. The rest are removed from the type. The function signature now reflects only what it actually handles.

Intermediate: API response handler

ts
type ApiResponse = | { status: "success"; data: string } | { status: "error"; message: string } | null | undefined; type ErrorResponse = Extract<ApiResponse, { status: "error" }>; // { status: "error"; message: string } function handleError(response: ErrorResponse) { console.log("Error:", response.message); // TypeScript knows .message exists here } function processResponse(response: ApiResponse) { if (response && response.status === "error") { handleError(response); // Assignable - the guard narrows to ErrorResponse } }

Extract gives you a precise type for the error branch. Without it, you would need a type assertion or a manual type guard that TypeScript cannot verify on its own.

Short Answer

Interview ready
Premium

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

Finished reading?