Deep Dive: Using Extract<T, U> to Extract Types from Unions
Introduction
Working with unions is a daily task for intermediate TypeScript developers. Often you have a large union type representing many possible shapes, and you need to narrow it down to a subset that matches particular criteria. TypeScript ships with a handful of utility and conditional types to address this need, and one of the most practical helpers is Extract<T, U>. Extract allows you to produce a subtype of T that is assignable to U — effectively "picking" members of a union. This is invaluable when modeling APIs, building DSLs, or writing resilient type-level code.
In this tutorial you'll learn: what Extract<T, U> does, how it works under the hood, common patterns like extracting discriminated variants, combining Extract with mapped types and generics, and advanced uses such as conditional filtering and composing with runtime validators. We'll include plenty of examples, step-by-step walkthroughs, and troubleshooting tips so you can apply these techniques immediately in your codebase.
We'll also connect Extract to broader type-system concepts, so if you're coming from a background of utility types or generics you'll find links to related, deeper material. By the end you'll understand not only how to use Extract safely, but when it’s the right tool and how to avoid common pitfalls.
Background & Context
Extract<T, U> is a built-in TypeScript utility type defined roughly as a conditional type that keeps members of T that are assignable to U. In essence it's a type-level filter over union members. Because unions are pervasive — API responses, event payloads, or discriminated unions — being able to express "give me only the members that match X" is a powerful capability.
Utility types like Extract live alongside other transformers such as Partial, Pick, and Record. If you want a broader refresher on how these transformers work and when to apply them, see our primer on introduction to utility types. Extract integrates well with the rest of the type-level toolbox and with generics, so understanding generics is helpful; see our guide on Introduction to Generics for core patterns.
Key Takeaways
- What Extract<T, U> does: filter union members by assignability.
- How to use Extract for discriminated unions and literal unions.
- Combining Extract with generics, mapped types, and conditional types.
- When Extract is better than type assertions or runtime checks.
- Pitfalls: excess narrowing, distributive conditional types, and unexpected never results.
Prerequisites & Setup
This guide assumes you have an intermediate-level understanding of TypeScript: basic generics, union and intersection types, mapped types, and conditional types. You should be using TypeScript 3.5+ (Extract was introduced earlier but modern TypeScript has better inference behavior). If you want to test code, create a new project or use the TypeScript playground (https://www.typescriptlang.org/play).
Editor: VS Code with the TypeScript extension recommended. Node and ts-node are optional if you want to run compiled snippets, but most examples will be purely type-level and validated through your editor. If you need a refresher on union and literal types, check our article on Using Union Types Effectively with Literal Types.
Main Tutorial Sections
1) Basic Usage: What Extract<T, U> Actually Does
Extract<T, U> keeps members of the union T that are assignable to U and removes the rest. It's shorthand for a distributive conditional type that checks each union member.
Example:
type All = "a" | "b" | 1 | 2; type Strings = Extract<All, string>; // "a" | "b" type Numbers = Extract<All, number>; // 1 | 2
Step-by-step: TypeScript distributes Extract over the union "a" | "b" | 1 | 2, checking assignability to string or number. If a member is assignable it stays; otherwise it's reduced to never and dropped. This behavior makes Extract predictable for literal unions and primitive unions.
When you model API-level discriminators or literal unions, Extract is a succinct filter.
2) Extract with Object Unions (Shapes)
Unions often contain object shapes. Extract is useful to pull objects matching a particular property type.
Example:
type Event =
| { type: 'click'; x: number; y: number }
| { type: 'keypress'; key: string }
| { type: 'mousemove'; x: number; y: number };
type PosEvents = Extract<Event, { x: number; y: number }>;
// { type: 'click'; x: number; y: number } | { type: 'mousemove'; x: number; y: number }Explanation: Extract matches by structural assignability. Both click and mousemove have x and y so they are kept. This is handy for creating helper types for subsets of events.
For more complex union-driven systems (where unions and intersections are used heavily), see patterns in typing libraries that use union and intersection types extensively.
3) Extract with Discriminated Unions
Discriminated unions have a literal "tag" field which makes narrowing simpler. Use Extract to pick variants by tag.
Example:
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
type Rect = Extract<Shape, { kind: 'rect' }>; // { kind: 'rect'; width: number; height: number }This is safer than type assertions and works well when building generic helpers that work on a specific tag. For deep dives on union design and best patterns, read our discussion about typing libraries using union and intersection types.
4) Extract vs Pick vs Omit vs Partial
Extract operates on unions. Pick/Omit/Partial operate on object properties. Understanding when to use each avoids confusion.
- Use Extract when you want a subset of union members.
- Use Pick/Omit to select or remove properties from a single object type.
- Use Partial to make properties optional (see our guide on Using Partial
: Making All Properties Optional ).
Example combination:
type User = { id: string; name: string; role: 'admin' | 'user' };
type AdminUser = Extract<User | { role: 'admin' }, { role: 'admin' }> // resolves to { id: string; name: string; role: 'admin' } | { role: 'admin' }While Extract can work here, Pick/Omit are more idiomatic for selecting properties of a single type.
5) Composing Extract with Mapped and Conditional Types
You can compose Extract with mapped types to produce selective transformations.
Example: Suppose you have a set of action creators and you want a type-level map of payload types keyed by action type.
type Action =
| { type: 'inc'; payload: number }
| { type: 'set'; payload: { value: string } }
| { type: 'noop' };
type Payload<T extends string> = Extract<Action, { type: T }> extends { payload: infer P } ? P : undefined;
type IncPayload = Payload<'inc'>; // number
type SetPayload = Payload<'set'>; // { value: string }This pattern leverages Extract to locate the member, then infer extracts payload types. For many such transformer patterns, the broader utility type discussion is helpful; see introduction to utility types.
6) Using Extract in Generic Functions
Extract is commonly used in function-level generics to constrain inputs and outputs. This avoids unsafe any or excessive overloading.
Example:
function handleEvent<T extends Action['type']>(type: T, payload: Payload<T>) {
// Type-safe: payload guaranteed to match type
}
handleEvent('inc', 1); // ok
handleEvent('set', { value: 'x' }); // ok
// handleEvent('inc', { value: 'x' }); // Type errorYou can read more about writing such generics and patterns in our guide on Generic Functions: Typing Functions with Type Variables, which covers inference and overload alternatives.
7) Constraints, Distributivity, and Unexpected never
Extract participates in distributive conditional types — when the checked type is a naked type parameter, TypeScript distributes the conditional over unions. This can cause surprising "never" results if U excludes all members.
Example:
type A = 'a' | 'b' | 'c'; type B = Extract<A, 'd'>; // never
When working in generics, sometimes you must add constraints to avoid distribution surprises. See our article on Constraints in Generics: Limiting Type Possibilities for patterns to guard type parameters.
A common trick is to wrap a union in a tuple/array to prevent distribution when you want to treat the union as a single unit.
8) Runtime Validation & Extract (Bridging Types to Runtime)
Types are erased at runtime, so Extract cannot run in production. When you need to assert that a runtime value matches an extracted union subset, combine Extract with a runtime validator: e.g., Zod or Yup. Use Extract to express the compile-time subset and Zod to check values at runtime.
Example:
import { z } from 'zod';
const actionSchema = z.union([
z.object({ type: z.literal('inc'), payload: z.number() }),
z.object({ type: z.literal('set'), payload: z.object({ value: z.string() }) }),
z.object({ type: z.literal('noop') }),
]);
// At compile-time
type IncAction = Extract<Action, { type: 'inc' }>;For integration patterns and runtime validation strategies, review our article on Using Zod or Yup for Runtime Validation with TypeScript Types (Integration).
9) Libraries, Overloads, and Complex Signatures
When authoring libraries, Extract is useful for narrowing large exported unions or for building helper APIs that accept specific variants. For example, you might expose a function that only accepts numeric events; using Extract keeps your public signatures tight.
If you're building complex generic APIs, investigate patterns in Typing Libraries With Complex Generic Signatures — Practical Patterns to combine Extract with other advanced tactics and preserve inference.
Example: building a registry keyed by event kind where generics map a key to an extracted type ensures callers get precise payload types without overloading.
Advanced Techniques
Once you're comfortable with Extract basics, use it in higher-order type utilities and composition patterns. Examples:
- Type-level filtering pipeline: create reusable helpers FilterBy<T, U> = Extract<T, U> and compose with other transforms like NonNullable or ReturnType.
- Multi-stage extraction: first narrow by property shape, then use infer to extract nested types (as we did with payload). This helps when building typed factories or registries.
- Prevent distribution when needed by wrapping unions in tuples: type NoDistribute
= [T] extends [infer U] ? U : never. - Combine Extract with mapped types to produce lookup tables: map discriminant -> payload type.
For optimizations, prefer narrow discriminators (literal fields) in your union design so Extract will match precisely and maintain good editor performance. If you author libraries, test inference across TypeScript versions; complex conditional types can regress in older compilers. See our guidance for library authors in Typing Libraries That Use Union and Intersection Types Extensively and Typing Libraries With Complex Generic Signatures — Practical Patterns.
Best Practices & Common Pitfalls
Do:
- Use consistent discriminators (e.g., kind/type) to make Extract patterns simple.
- Prefer compile-time extraction + runtime validation when dealing with external input.
- Use Extract to keep public APIs narrowly typed instead of relying on any or type assertions.
Don't:
- Use Extract as a runtime check — it doesn't exist at runtime.
- Expect Extract to deep-compare shapes; it’s structural and checks assignability.
- Overcomplicate simple patterns: sometimes a small type guard or explicit overload is clearer than a dense type-level expression.
Troubleshooting tips:
- If Extract resolves to never, check that U actually matches any union members. Debug by testing smaller U values.
- If inference fails in functions, try explicit type parameters or factory wrappers. For function-level design patterns, see Generic Functions: Typing Functions with Type Variables.
Real-World Applications
-
Event routers: Extract the subset of events handled by a specific handler group and infer payload types for dispatch functions. This reduces runtime errors and improves autocomplete.
-
API clients: When different API endpoints return overlapping unions, Extract helps express the shape of responses per endpoint type; pair with runtime checks described in Typing API Request and Response Payloads with Strictness.
-
Form builders: Use Extract to narrow a union of form control configs to a control type expected by a renderer; combine with Partial
patterns for optional config pieces — see Using Partial : Making All Properties Optional .
These patterns keep codebases maintainable and reduce the need for unsafe casts.
Conclusion & Next Steps
Extract<T, U> is a concise, powerful tool for type-level filtering of unions. Use it to narrow variants, infer nested payloads, and build safer generic APIs. Next, practice by refactoring a small portion of your codebase that uses unions or build a typed event registry. To expand your knowledge, read more about utility types and generics in the linked resources throughout this article.
Enhanced FAQ
Q1: What’s the difference between Extract and Exclude?
A: Extract<T, U> keeps members of T assignable to U. Exclude<T, U> removes members of T assignable to U. They are complementary: Extract<T, U> = T extends U ? T : never, while Exclude uses the negated branch. Use Exclude when you want to remove matches; use Extract when you want to keep only matches.
Q2: Does Extract work with nested types (deep matching)?
A: Extract checks structural assignability at the top-level shape you provide. It doesn't recursively "deep match" unless your U explicitly expresses nested constraints. To match nested properties, describe them in U (e.g., Extract<T, { nested: { key: string } }>). For complex deep checks consider runtime validators.
Q3: Why did Extract give me never?
A: "never" means no member of T is assignable to U. Confirm U is correct and try testing with simpler subsets. If you're in a generic context, distribution may cause unexpected never results — wrap the type in a tuple to avoid distribution or add constraints.
Q4: Can I use Extract inside mapped types?
A: Yes. You can use Extract in mapped type value positions to produce selective transforms. For instance, mapping over keys to derive a payload type using Extract and infer is a common pattern.
Q5: Is Extract runtime-safe?
A: No — Extract is erased at compile-time. To ensure runtime safety, pair the type with runtime validation (Zod, Yup) as covered in Using Zod or Yup for Runtime Validation with TypeScript Types (Integration).
Q6: How does Extract behave with literal unions and string/number unions?
A: Extract is ideal for literal unions. It keeps only the literals that match. For example, Extract<'a'|'b'|1|2, 'a'|'b'> results in 'a'|'b'. It's distributive and straightforward for primitive literal unions.
Q7: Should library authors expose Extract-based helpers?
A: Yes, when you need to provide narrow, type-safe APIs over larger unions. But test cross-version inference and document expected type parameters — see our guidance in Typing Libraries With Complex Generic Signatures — Practical Patterns.
Q8: How do I debug complex conditional types involving Extract?
A: Break your type expressions into smaller intermediate types and inspect them in the TypeScript playground or your editor. Add temporary aliases and use type assertions to compare results. Use tools like ts-toolbelt for additional diagnostics, and consult articles on generic constraints (see Constraints in Generics).
Q9: Can Extract help with overloading or narrowing function inputs?
A: Absolutely. Use Extract in generic parameters to map an input discriminator to a precise payload type instead of writing many overloads. See examples in the "Using Extract in Generic Functions" section and expand with patterns from Generic Functions: Typing Functions with Type Variables.
Q10: Are there performance concerns with heavy use of Extract?
A: Complex conditional types may slow down editor type checking for very large codebases. Keep types comprehensible, prefer discriminated unions with narrow tags, and test performance. For library-level heavy lifting, consider simplifying public types where possible and provide internal helpers for complex logic.
If you want to continue learning, explore our other guides on generic interfaces and classes for more patterns that pair well with Extract: Generic Interfaces: Creating Flexible Type Definitions and Generic Classes: Building Classes with Type Variables.
