M Logo
๐Ÿ“ TypeScript from Zero ยท Part 42 of 50

TypeScript Conditional Types and infer: T extends U ? X : Y

November 6, 2026

Welcome to episode 42 of TypeScript from Zero, a series for complete beginners (no JavaScript needed). Welcome back to TypeScript from zero! A conditional type is an if statement for types. Given a type, it picks one of two answers. Combined with the word infer, it can even take types apart. A new file called conditional.ts in the tasks project. Every code block below is the exact code and output from the video, ready to copy.

Setup

We start in the terminal and open the project in VS Code:

bash
cd tasks
touch src/conditional.ts
code .

1. If for types

It looks like the ternary operator from episode six. T extends string, question mark, yes, colon, no. If T fits into string, the answer is the text type yes, else no. For string it is yes, so a is fine. For number it is no, so giving yes is an error: type yes is not assignable to type no. TS2322. The error tells us what the answer was.

src/conditional.ts:

ts
type IsString<T> = T extends string ? "yes" : "no";
const a: IsString<string> = "yes";
const b: IsString<number> = "yes";
text
$ npx tsc --noEmit
src/conditional.ts:3:7 - error TS2322: Type '"yes"' is not assignable to type '"no"'.

3 const b: IsString<number> = "yes";
        ~

Found 1 error in src/conditional.ts:3

2. Fix

We write no, and tsc is quiet.

src/conditional.ts:

ts
type IsString<T> = T extends string ? "yes" : "no";
const a: IsString<string> = "yes";
const b: IsString<number> = "no";
bash
$ npx tsc --noEmit

3. infer

Infer declares a new type variable inside the condition. Element of takes T, and asks: is it a list of something? Call that something U. If yes, the answer is U. So element of a list of numbers is number, and the five is fine. For a list of strings, the number one is an error, and the message says: not assignable to type string. That is U, found by tsc.

src/conditional.ts:

ts
type IsString<T> = T extends string ? "yes" : "no";
const a: IsString<string> = "yes";
const b: IsString<number> = "no";
type ElementOf<T> = T extends (infer U)[] ? U : never;
const e: ElementOf<number[]> = 5;
const bad: ElementOf<string[]> = 1;
text
$ npx tsc --noEmit
src/conditional.ts:6:7 - error TS2322: Type 'number' is not assignable to type 'string'.

6 const bad: ElementOf<string[]> = 1;
        ~~~

Found 1 error in src/conditional.ts:6

4. Fix

We use a string, and it works.

src/conditional.ts:

ts
type IsString<T> = T extends string ? "yes" : "no";
const a: IsString<string> = "yes";
const b: IsString<number> = "no";
type ElementOf<T> = T extends (infer U)[] ? U : never;
const e: ElementOf<number[]> = 5;
const bad: ElementOf<string[]> = "one";
bash
$ npx tsc --noEmit

5. Unwrap

The same trick takes a value out of a promise. Unwrap asks: is T a promise of R? Then the answer is R. Otherwise, the answer is T itself. Promise of string unwraps to string, and a plain number stays a number. This is how awaited from episode thirty two is built.

src/conditional.ts:

ts
type IsString<T> = T extends string ? "yes" : "no";
const a: IsString<string> = "yes";
const b: IsString<number> = "no";
type ElementOf<T> = T extends (infer U)[] ? U : never;
const e: ElementOf<number[]> = 5;
const bad: ElementOf<string[]> = "one";
type Unwrap<T> = T extends Promise<infer R> ? R : T;
const u: Unwrap<Promise<string>> = "ok";
const v: Unwrap<number> = 7;
console.log(a, b, e, bad, u, v);
text
$ node src/conditional.ts
yes no 5 one ok 7

6. Unions

One surprise. When T is a union, a conditional type is applied to every member separately. To array of string or number is not a list of mixed things, it is a list of strings, or a list of numbers. We see it in the error: an item must be a string or a number, and a boolean is neither. That is called a distributive conditional type, and mostly it is what we want. We delete the line, and tsc is quiet.

src/conditional.ts:

ts
type IsString<T> = T extends string ? "yes" : "no";
const a: IsString<string> = "yes";
const b: IsString<number> = "no";
type ElementOf<T> = T extends (infer U)[] ? U : never;
const e: ElementOf<number[]> = 5;
const bad: ElementOf<string[]> = "one";
type Unwrap<T> = T extends Promise<infer R> ? R : T;
const u: Unwrap<Promise<string>> = "ok";
const v: Unwrap<number> = 7;
console.log(a, b, e, bad, u, v);
type ToArray<T> = T extends unknown ? T[] : never;
const x: ToArray<string | number> = [true];
text
$ npx tsc --noEmit
src/conditional.ts:12:38 - error TS2322: Type 'boolean' is not assignable to type 'string | number'.

12 const x: ToArray<string | number> = [true];
                                        ~~~~

Found 1 error in src/conditional.ts:12

Recap

Today: conditional types as an if for types, infer to capture a part of a type, unwrapping a promise, and the distribution over unions. Next episode: template literal types.

7. `string

numberis split:string[]

text
$ npx tsc --noEmit
src/conditional.ts:12:38 - error TS2322: Type 'boolean' is not assignable to type 'string | number'.

12 const x: ToArray<string | number> = [true];
                                        ~~~~

Found 1 error in src/conditional.ts:12

ยฉ 2026 All Rights Reserved By Mahmudul Alam