JS Guide
HomeQuestionsTopicsCompaniesResources
BookmarksSearch

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeTopicstypescriptConditional Types
PrevNext
typescript
advanced
10 min read

Conditional Types

advanced
conditional-types
distributive
infer
type-level-programming
utility-types

Conditional types use the syntax T extends U ? X : Y to branch at the type level — combined with the infer keyword to extract types from patterns and distributive behavior over unions, they power most of TypeScript's built-in utility types.

Key Points

1Type-Level Branching

T extends U ? X : Y — if T is assignable to U, resolve to X, otherwise Y. The extends keyword is a subtype check.

2infer Keyword

Declares type variables inside extends clauses — pattern-matches against types to extract return types, element types, or any structural component.

3Distributive Behavior

When applied to unions, conditional types evaluate each member separately — ToArray<A | B> becomes ToArray<A> | ToArray<B>. Wrap in tuple to prevent.

4Built-in Utilities

Exclude, Extract, ReturnType, Parameters, Awaited, and NonNullable are all implemented as conditional types in TypeScript's standard library.

What You'll Learn

  • Write conditional types with the T extends U ? X : Y syntax
  • Use the infer keyword to extract types from function signatures and other patterns
  • Explain distributive behavior of conditional types over unions
  • Implement practical type transformations using conditional types

Deep Dive

Conditional types bring if/else logic to the type system. They let you create types that depend on other types — selecting one branch or another based on a type relationship.

Basic Syntax

The syntax mirrors JavaScript's ternary operator: T extends U ? X : Y. If T is assignable to U, the type resolves to X; otherwise Y.

TSX
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>;      // false

The extends in conditional types means "is assignable to" — it's a subtype check, not equality.

The infer Keyword

infer declares a type variable inside the extends clause that TypeScript infers from the matched pattern. It's like pattern matching for types.

TSX
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ElementType<T> = T extends (infer E)[] ? E : never;
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

infer R in ReturnType tells TypeScript: "match against a function signature and bind whatever the return type is to R." If the match succeeds, return R; otherwise never.

infer can only be used in the extends clause of a conditional type. Multiple infer positions in the same clause are allowed — TypeScript infers each independently.

Distributive Conditional Types

When a conditional type is applied to a union, it distributes — the condition is evaluated for each union member separately:

TSX
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]

Without distribution, the result would be (string | number)[] — a single array of mixed types. Distribution applies the transformation to each member individually.

Distribution only happens when T is a naked type parameter (directly from a generic). To prevent distribution, wrap in a tuple: [T] extends [any] ? ... : ....

Built-in Conditional Utility Types

TypeScript's standard library uses conditional types extensively:

  • Exclude<T, U> — Remove types from T assignable to U: Exclude<'a' | 'b' | 'c', 'a'> → 'b' | 'c'
  • Extract<T, U> — Keep only types from T assignable to U
  • NonNullable<T> — Remove null | undefined (uses Exclude internally)
  • ReturnType<T> — Extract return type of a function type
  • Parameters<T> — Extract parameter types as a tuple
  • Awaited<T> — Recursively unwrap Promises

Practical Patterns

Conditional types enable sophisticated type transformations:

TSX
// Extract event handler types
type EventMap = { click: MouseEvent; keydown: KeyboardEvent };
type HandlerFor<K extends keyof EventMap> = (event: EventMap[K]) => void;
 
// Flatten nested types
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;

Recursive conditional types (supported since TypeScript 4.1) enable deep transformations like DeepPartial, DeepReadonly, and JSON type parsers.

Key Interview Distinction

Conditional types use T extends U ? X : Y to branch at the type level. The infer keyword extracts types from patterns (like function return types or array elements). Conditional types distribute over unions by default — each union member is evaluated separately. Most built-in utility types (Exclude, ReturnType, NonNullable) are implemented with conditional types.

Fun Fact

The `infer` keyword was added in TypeScript 2.8 (2018) and is considered one of the most powerful features in the type system. It enabled an entire ecosystem of type-level programming — libraries like ts-toolbelt and type-fest use infer extensively to implement hundreds of utility types, and some developers have even built type-level parsers and interpreters using recursive conditional types with infer.

Learn These First

Generics

intermediate

Union & Intersection Types

beginner

Continue Learning

Mapped Types

advanced

Utility Types

intermediate

Template Literal Types

advanced

Practice What You Learned

How do conditional types work in TypeScript?
senior
conditional-types
Conditional types select types based on conditions using the syntax T extends U ? X : Y. They enable type-level programming, allowing different types based on input. Combined with infer, they can extract and transform types dynamically.
Previous
TypeScript Fundamentals
Next
Declaration Files
PrevNext