Type-Level Conditionals
Branch on types with conditional type expressions.
Type-Level Conditionals is a free TypeScript Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Type-Level If
The type language gains branching with conditional types. The syntax T extends U ? X : Y reads: if T is assignable to U, the result is X, otherwise it is Y.
This is the type-level equivalent of an if / else expression.
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"extends Means Assignable
The test is not equality. T extends U is true when a value of type T could be used where a U is expected. Literal types are assignable to their base type.
type T1 = "hello" extends string ? true : false; // true
type T2 = string extends "hello" ? true : false; // false
type T3 = 42 extends number ? true : false; // trueChoosing a Result Type
Conditionals let one generic return different shapes depending on input. Here Wrap wraps arrays differently from scalars.
type Wrap<T> = T extends unknown[]
? { list: T }
: { value: T };
type A = Wrap<number>; // { value: number }
type B = Wrap<string[]>; // { list: string[] }Filtering to never
A common trick is to return never in one branch. never means "no value", and it is useful for removing members from unions later.
type OnlyStrings<T> = T extends string ? T : never;
type A = OnlyStrings<string>; // string
type B = OnlyStrings<number>; // neverInferring With infer
The real power appears with the infer keyword. Inside a conditional, infer introduces a fresh type variable that captures part of the matched type.
Here we capture the element type of an array.
type ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<number[]>; // number
type B = ElementType<string[]>; // stringInferring Function Results
You can place infer anywhere in the pattern. To extract a function return type, infer the part after the arrow. This is how the built-in ReturnType works.
type MyReturn<T> = T extends (...args: any[]) => infer R ? R : never;
type A = MyReturn<() => number>; // number
type B = MyReturn<(x: string) => boolean>; // booleanInferring Multiple Pieces
A single conditional can introduce several infer variables at once. Here we pull both the first and the rest of a tuple.
type FirstRest<T> = T extends [infer H, ...infer R]
? { head: H; rest: R }
: never;
type A = FirstRest<[1, 2, 3]>;
// { head: 1; rest: [2, 3] }Nested Conditionals
Conditionals nest just like chained else if. The else branch of one conditional can itself be another conditional, forming a decision ladder.
type Describe<T> =
T extends string ? "text" :
T extends number ? "num" :
T extends boolean ? "flag" :
"other";
type A = Describe<number>; // "num"
type B = Describe<null>; // "other"Conditions as Guards
Use a conditional to verify a shape before extracting from it. If the input does not match, fall back to never so misuse is visible.
type GetName<T> = T extends { name: infer N } ? N : never;
type A = GetName<{ name: string }>; // string
type B = GetName<{ age: number }>; // neverCombining Conditions
You can require multiple conditions by nesting them. Here a type must be both an object and have an id to pass.
type RequireId<T> =
T extends object
? T extends { id: unknown } ? T : never
: never;
type A = RequireId<{ id: 1; x: 2 }>; // { id: 1; x: 2 }
type B = RequireId<{ x: 2 }>; // neverConditionals Are Everywhere
Most built-in utility types are conditionals underneath: NonNullable, Extract, Exclude, Parameters, and ReturnType. Understanding extends ? : unlocks them all.
type MyNonNullable<T> = T extends null | undefined ? never : T;
type A = MyNonNullable<string | null>; // stringQuick Check
Test your understanding of conditional types and infer.
Recap
Conditional types give the type language an if/else and pattern matching.
T extends U ? X : Ybranches on assignability.infercaptures matched parts in fresh variables.- Conditionals nest to form decision ladders.
- Returning
neverfilters members out.
Next: feeding a conditional back into itself for recursion.
Frequently asked questions
Is the “Type-Level Conditionals” lesson free?
Yes — the full text of “Type-Level Conditionals” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Type-Level Conditionals”?
Branch on types with conditional type expressions. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Type-Level Conditionals” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Types as a Computation Language
- Type-Level Conditionals
- Type-Level Recursion
- Distributive Conditional Types