Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Conditional Types and infer

Learn TypeScript conditional types: the T extends U ? X : Y syntax, distribution over unions, how Exclude, Extract and NonNullable work, extracting types with infer, unwrapping arrays and promises, and keeping conditional types readable.

Advanced6 min readUpdated
On this page

A conditional type chooses between two types based on a condition, like a ternary operator for types:

TypeScript
T extends U ? X : Y

"If T is assignable to U, the type is X; otherwise it is Y." Conditional types are mostly used inside generic types to compute one type from another.

A first example#

TypeScript
type IsString<T> = T extends string ? "yes" : "no";

const a: IsString<"hello"> = "yes";
const b: IsString<42> = "no";
console.log(a, b);
Output
yes no
TypeScript
type IsString<T> = T extends string ? "yes" : "no";
const c: IsString<number> = "yes";
Compiler error
error TS2322: Type '"yes"' is not assignable to type '"no"'.

A practical example#

Return a different type depending on the input type:

TypeScript
type Wrapped<T> = T extends unknown[] ? T : T[];

function toArray<T>(value: T): Wrapped<T> {
  return (Array.isArray(value) ? value : [value]) as Wrapped<T>;
}

const one = toArray("tea");            // string[]
const many = toArray([1, 2]);          // number[]
console.log(one, many);
Output
[ 'tea' ] [ 1, 2 ]

The implementation needs a type assertion (as Wrapped<T>), because TypeScript cannot follow a runtime check into an unresolved conditional type. This is common when implementing functions with conditional return types.

Distribution over unions#

When the checked type is a bare type parameter and you pass a union, the condition is applied to each member separately, and the results are combined:

TypeScript
type ToArray<T> = T extends unknown ? T[] : never;

type R = ToArray<string | number>;      // string[] | number[]   (not (string | number)[])

const r1: R = ["a", "b"];
const r2: R = [1, 2];
console.log(r1.length + r2.length);
Output
4

This is exactly how the built-in Exclude and Extract work:

TypeScript
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;

type Status = "draft" | "review" | "published";
type Live = MyExclude<Status, "draft">;          // "review" | "published"
type OnlyDraft = MyExtract<Status, "draft">;     // "draft"

const live: Live = "published";
const draft: OnlyDraft = "draft";
console.log(live, draft);
Output
published draft

Returning never for a member removes it from the resulting union. To turn distribution off, wrap both sides in brackets: [T] extends [U] ? X : Y.

infer: extracting a type#

Inside the condition, infer declares a type variable that TypeScript fills in by matching:

TypeScript
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Unpromise<T> = T extends Promise<infer V> ? V : T;
type FirstArg<F> = F extends (first: infer A, ...rest: never[]) => unknown ? A : never;

type E = ElementOf<string[]>;                     // string
type V = Unpromise<Promise<{ id: number }>>;      // { id: number }
type A = FirstArg<(name: string, age: number) => void>;   // string

const e: E = "item";
const v: V = { id: 1 };
const arg: A = "Asha";
console.log(e, v.id, arg);
Output
item 1 Asha

Read T extends Promise<infer V> ? V : T as: "if T is a promise of something, call that something V and return it; otherwise return T". The built-in ReturnType, Parameters and Awaited are written this way.

Recursive conditional types#

Conditional types can refer to themselves, for example to unwrap nested arrays completely:

TypeScript
type DeepElement<T> = T extends readonly (infer E)[] ? DeepElement<E> : T;

type Deep = DeepElement<number[][][]>;   // number

const d: Deep = 42;
console.log(d);
Output
42

Keep them readable#

Conditional types are powerful, but heavy type-level code is hard to read and slow down the compiler. Prefer the built-in utility types where possible, give intermediate types descriptive names, and add a short comment explaining what a complex type computes. Most application code needs only a few simple conditional types; libraries use them more.

Practice#

Practice exercise

Write type ApiData<T> = T extends { data: infer D } ? D : never, and use it to extract the data type from type UserResponse = { status: number; data: { id: number; name: string } }. Declare a value of the extracted type and print it.

Show solution
TypeScript
type ApiData<T> = T extends { data: infer D } ? D : never;

type UserResponse = { status: number; data: { id: number; name: string } };
type UserData = ApiData<UserResponse>;     // { id: number; name: string }

const user: UserData = { id: 7, name: "Meera" };
console.log(user);
Output
{ id: 7, name: 'Meera' }

Key takeaway#

Finished this lesson?