TypeScript

TypeScript Conditional Types👨‍💻

Conditional types let you write if/else logic at the type level. They power every utility type you already use — ReturnType, Exclude, Awaited, Parameters — and once you understand the three core mechanics (extends checks, infer, and distribution over unions), you can build type utilities that eliminate entire categories of bugs from your codebase.

Key Takeaways

  • 1The syntax `T extends U ? X : Y` checks assignability: if `T` is assignable to `U`, the type resolves to `X`, otherwise `Y`
  • 2The `infer` keyword declares a type variable inside an `extends` clause that TypeScript fills in from context — this is how you extract return types, promise contents, and tuple elements
  • 3When a conditional type receives a union and the checked type is a naked (unwrapped) type parameter, it distributes: each union member is checked independently
  • 4Wrap both sides in a tuple (`[T] extends [U]`) to prevent distribution and check the union as a whole
  • 5Returning `never` from a distributive conditional removes that member from the union — this is exactly how `Exclude` and `Extract` work under the hood
  • 6Conditional types compose with mapped types and template literal types for patterns like filtering object keys by value type or transforming API response shapes

Master typescript conditional types

Take the TypeScript Type System Mastery course with hands-on lessons and challenges.

Examples

Unwrapping API response types with infer

typescript

The infer keyword captures the generic argument inside ApiResponse. This is useful when your API client returns wrapped responses and you need the inner type for components or tests without manually extracting it.

Type-safe event handler extraction from a component props interface

typescript

Combines key remapping (filtering keys starting with 'on') with infer to extract handler argument types. This pattern shows up in component testing utilities and form libraries where you need to programmatically call handlers with correctly typed arguments.

Distributive conditionals — filtering union members

typescript

Extract distributes over the union and keeps only members assignable to the filter shape. This is the standard approach for narrowing discriminated unions in event systems, Redux actions, or webhook payload handlers.

Deep promise unwrapping with recursive conditional types

typescript

Recursive conditional types keep unwrapping until the base case is reached. The built-in Awaited type works this way. The OrderService example shows how to derive a resolved return type from a class method — useful for typing service layers without exporting extra interfaces.

Preventing distribution with tuple wrapping

typescript

Wrapping both sides of extends in a tuple stops TypeScript from distributing over union members. The IsNever example is the canonical case where this matters — without the wrapper, never distributes to zero iterations and the result is never instead of true.

Filtering object keys by value type

typescript

The mapped type checks each key's value against the target type, replacing non-matching keys with never. The indexed access [keyof T] collapses the result into a union of the surviving keys. This pattern is common in form libraries and ORM query builders where you need to select fields by type.

Common Mistakes

Mistake:

Expecting a union to be checked as a whole, then getting surprised by distributive behavior — e.g., `IsString<string | number>` returning `boolean` instead of `false`

Fix:

Wrap both sides in a tuple when you want to check the entire union: `[T] extends [string] ? true : false`. Distribution is the default for naked type parameters. Learn to recognize when you want it and when you do not.

Mistake:

Using `infer` outside of an `extends` clause — e.g., writing `type X = infer T` or trying to use it in a mapped type directly

Fix:

The `infer` keyword only works inside the condition of a conditional type (`T extends ... infer R ... ? ... : ...`). There is no other valid position for it. If you need type extraction elsewhere, write a conditional type helper and call it.

Mistake:

Writing complex nested conditional chains (3+ levels deep) that become unreadable — e.g., `T extends A ? X : T extends B ? Y : T extends C ? Z : W`

Fix:

Extract each branch into a named type alias. Instead of one giant conditional, write `type HandleA<T> = ...`, `type HandleB<T> = ...`, then compose them. Readability matters more than cleverness at the type level too.

Mistake:

Forgetting that `never` is the empty union — passing `never` to a distributive conditional returns `never` (zero iterations), not the false branch

Fix:

If your utility might receive `never`, use the tuple wrapper: `[T] extends [never] ? FallbackType : ...`. This is especially important for utilities consumed by other generic types where `never` can propagate unexpectedly.

Best Practices

  • Reach for built-in utility types first — `ReturnType`, `Parameters`, `Awaited`, `Extract`, and `Exclude` cover the vast majority of conditional type use cases without custom code
  • Use distributive conditionals intentionally: naked type parameters for filtering union members, tuple-wrapped parameters for checking unions as a whole
  • Keep conditional types to one or two levels of nesting. If you need more branches, extract named type aliases for each — future you will thank present you during debugging
  • Combine conditional types with mapped types for the most powerful patterns (filtering keys by value type, transforming specific properties), but ask yourself if a simpler `Pick` or `Omit` achieves the same result first
  • Test conditional types with explicit type assertions (`type _test = Expect<Equal<YourType<Input>, Expected>>`) — type-level logic has edge cases around `never`, `any`, and empty unions that are easy to miss

Summary

Conditional types add branching logic to the type system with `T extends U ? X : Y`. The `infer` keyword extracts types from within complex structures — promise contents, function signatures, tuple elements. Distributive behavior automatically maps over union members, which is how `Exclude` and `Extract` work. Wrap in `[T] extends [U]` when you need to check a union as a whole. In practice, you will use conditional types most often for API response unwrapping, discriminated union filtering, and deriving types from existing code without manual duplication.

Practice TypeScript with hands-on challenges

Learn typescript conditional types hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master TypeScript with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.