Chapter 27: TypeScript Conditional Types
Conditional Types in TypeScript like we’re sitting together in a calm room, drawing on a whiteboard, slowly building understanding from simple to powerful real patterns.
Conditional types are one of the most important building blocks of advanced TypeScript. They appeared in TypeScript 2.8 (2018) and quickly became the foundation for almost every sophisticated type utility you see today (including most built-in Utility Types like Extract, Exclude, NonNullable, ReturnType, etc.).
1. The absolute simplest mental model
Think of conditional types as the ternary operator (? 🙂 but for types.
JavaScript:
|
0 1 2 3 4 5 6 |
const result = condition ? valueIfTrue : valueIfFalse; |
TypeScript (conditional type):
|
0 1 2 3 4 5 6 |
type Result = Condition ? TypeIfTrue : TypeIfFalse; |
But the condition is not a boolean value — it’s a type relationship test using the keyword extends.
|
0 1 2 3 4 5 6 7 |
type Example = "hello" extends string ? "YES string" : "NO"; // → "YES string" |
2. Basic syntax & meaning
|
0 1 2 3 4 5 6 7 8 9 10 |
type NameOrId<T> = T extends string ? string // T is some kind of string : T extends number ? number // T is some kind of number : never; // fallback — nothing else allowed |
|
0 1 2 3 4 5 6 7 8 |
type A = NameOrId<"alice">; // string type B = NameOrId<123>; // number type C = NameOrId<boolean>; // never ← good error prevention |
Key insight #1 extends here means “is assignable to” (structural compatibility), not strict equality.
|
0 1 2 3 4 5 6 |
type IsDog = Dog extends Animal ? true : false; // true (because Dog extends Animal) |
3. The real power — infer keyword
infer lets you declare a type variable inside the conditional and capture part of the input type.
Classic built-in example — ReturnType (TypeScript copies this pattern)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type Fn1 = () => string; type R1 = MyReturnType<Fn1>; // string type Fn2 = (a: number) => Promise<boolean>; type R2 = MyReturnType<Fn2>; // Promise<boolean> |
Very common real usage — unwrap Promise(s) recursively
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
type Awaited<T> = T extends Promise<infer U> ? U extends Promise<infer V> ? Awaited<V> // recurse : U : T; type A = Awaited<Promise<string>>; // string type B = Awaited<Promise<Promise<number>>>; // number type C = Awaited<Promise<Promise<Promise<Date>>>>; // Date |
4. Distributive conditional types (the tricky but powerful behavior)
When the checked type is a union, conditional types become distributive (they apply to each member separately).
|
0 1 2 3 4 5 6 7 8 9 10 |
type ToArray<T> = T extends any ? T[] : never; type StrOrNumArray = ToArray<string | number>; // string | number → string[] | number[] // NOT (string | number)[] |
Very useful pattern — filter union members
|
0 1 2 3 4 5 6 7 8 9 |
type ExtractString<T> = T extends string ? T : never; type OnlyStrings = ExtractString<"a" | 123 | true | "hello">; // → "a" | "hello" |
(This is exactly how built-in Extract<T, U> works)
5. Realistic production patterns in 2025–2026
Pattern 1: Function overload helper (very common in libraries)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
type OverloadReturn<T> = T extends { (): infer R1; (a: any): infer R2; (a: any, b: any): infer R3; } ? R1 | R2 | R3 : never; |
Pattern 2: Deep partial / mutable / readonly helpers
|
0 1 2 3 4 5 6 7 8 |
type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T; |
Pattern 3: If / Unless type (very clean control flow at type level)
|
0 1 2 3 4 5 6 7 8 |
type If<C extends boolean, T, F> = C extends true ? T : F; type HasAdmin = If<true, "Admin panel", "Guest view">; // "Admin panel" |
Pattern 4: API shape based on method (very frequent in SDKs / tRPC-like code)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
type Method = "GET" | "POST" | "PUT" | "DELETE"; type ResponseShape<M extends Method> = M extends "GET" | "DELETE" ? { data: unknown } : M extends "POST" | "PUT" ? { id: string; success: boolean } : never; |
Pattern 5: Exclude nullish + unwrap (real API / form handling)
|
0 1 2 3 4 5 6 7 8 9 10 |
type NonNullableDeep<T> = T extends object ? { [K in keyof T]: NonNullableDeep<NonNullable<T[K]>> } : NonNullable<T>; type CleanUser = NonNullableDeep<User | null | undefined>; |
6. Quick cheat-sheet table (keep in your notes)
| Pattern / Goal | Typical Conditional Type syntax | Since TS version | Very common in… |
|---|---|---|---|
| Basic yes/no decision | T extends U ? X : Y | 2.8 | Almost everywhere |
| Extract inner type | T extends Something<infer Inner> ? Inner : never | 2.8 | Promise, Array, ReturnType, etc. |
| Recursive unwrap | Nested infer + self-reference | 2.8+ | Awaited, DeepPartial, DeepReadonly |
| Distributive filter | T extends U ? T : never (applied to union) | 2.8 | Extract, Exclude, NonNullable |
| Different output based on literal | T extends “A” ? TypeA : T extends “B” ? TypeB : never | 2.8 | Discriminated unions, variants |
| Combine with mapped types | { [K in keyof T]: T[K] extends string ? number : T[K] } | 2.8+ | Transform object shapes |
7. Common gotchas / things that surprise people
- Unions are distributed → sometimes you need [T] extends [U] trick to prevent distribution
- never in false branch is very common (acts like filter)
- Deep recursion can hit type instantiation limit (error TS2589) — increase with –maxNodeModuleJsDepth or simplify
- infer only works in extends clause (not in true/false branches directly)
Your mini homework (try in playground right now)
- Write your own MyAwaited<T> that unwraps nested Promises
- Create OnlyFunctions<T> that keeps only function members of an object type
- Build IfEquals<A, B, Yes, No> that returns Yes only if A === B (literal equality)
Which pattern feels most useful or most confusing for you right now?
Want to:
- See how conditional types power most built-in utility types
- Deep dive into distributive behavior with tricks to disable it
- Build a small type-safe router / path params extractor
- Compare conditional types vs satisfies vs generics constraints
Just tell me — we’ll continue from exactly there! 😄
