7.5 — Conditional Types
Conditional types choose types based on conditions, like a flyover diversion
Conditional types let you create types that DEPEND on a condition — they are type-level if-statements. The syntax is T extends U ? X : Y: if T is assignable to U, the result is X; otherwise, it's Y. This simple concept unlocks some of the most powerful patterns in advanced TypeScript.
Think of the flyover diversion near Begumpet. If there's traffic (T extends Traffic), you take the flyover (result X). If there's no traffic (else), you take the ground route (result Y). The route you take DEPENDS on the condition at that moment. TypeScript evaluates this at the TYPE level — when you write type Result = string extends number ? "yes" : "no", TypeScript checks: "Is string assignable to number? No." So Result becomes the literal type "no".
But here is the real power: when T is a GENERIC type parameter, TypeScript doesn't evaluate immediately. Instead, it DISTRIBUTES the condition across union members, like a traffic cop routing each vehicle individually based on its type. Auto? Take the service road. Car? Take the flyover. Bike? Take the side lane. Each type gets its own route based on the condition!
This behavior, called distributive conditional types, is the secret sauce behind built-in utility types like Exclude, Extract, and NonNullable. Without conditional types, TypeScript's type system would be static and rigid. With them, your types become dynamic, adaptable, and intelligent. They can react to the specific shape of the data flowing through your code. Conditional types are the ultimate jugaad — clever, flexible solutions to complex type problems that keep your codebase safe and expressive, bhai!
The basic syntax of a conditional type mirrors a ternary operator: T extends U ? X : Y. Let's start with static examples to see how TypeScript evaluates them:
type IsString =
string extends number
? "yes" : "no";
// IsString is "no"
type IsString2 =
string extends string
? "yes" : "no";
// IsString2 is "yes"
Static types are evaluated immediately. The real magic happens with generics, where the type is evaluated based on what T is passed:
type IsString<T> =
T extends string ? "yes" : "no";
type A = IsString<string>;
// "yes"
type B = IsString<number>;
// "no"
A practical example is unwrapping array types. We use the infer keyword (covered in detail next chapter) to extract the inner type:
type UnwrapArray<T> =
T extends Array<infer U> ? U : T;
type R1 = UnwrapArray<string[]>;
// string
type R2 = UnwrapArray<number>;
// number (not an array, so T)
You can nest conditional types to handle multiple cases, building a type-level switch statement:
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
TypeScript includes several built-in conditional types. Exclude<T, U> removes types from a union, Extract<T, U> selects types from a union, and NonNullable<T> removes null and undefined:
type Exclude<T, U> =
T extends U ? never : T;
type Extract<T, U> =
T extends U ? T : never;
type NonNullable<T> =
T extends null | undefined
? never : T;
These built-ins are conditional types under the hood. Understanding their mechanics lets you build your own powerful type utilities!
When T is a NAKED type parameter (not wrapped in a tuple, array, or anything else), conditional types DISTRIBUTE across union members. This is the most important and most confusing behavior of conditional types, bhai! Seedha samjho: TypeScript applies the condition to EACH member of the union separately and combines the results with |.
type ToArray<T> =
T extends any ? T[] : never;
type Result =
ToArray<string | number>;
// string[] | number[]
// NOT (string | number)[]
TypeScript splits the union: it checks string extends any ? string[] : never (gives string[]), then number extends any ? number[] : never (gives number[]), and unions them: string[] | number[]. It's like the traffic cop routing each vehicle individually — one at a time!
This is exactly how Exclude works:
type Exclude<T, U> =
T extends U ? never : T;
type Result =
Exclude<"a" | "b" | "c", "a">;
// Step 1: "a" extends "a" ? never
// Step 2: "b" extends "a" ? "b"
// Step 3: "c" extends "a" ? "c"
// Final: "b" | "c"
"a" is excluded because it extends "a" and maps to never, which disappears from the union. "b" and "c" don't extend "a", so they map to themselves.
But what if you DON'T want distribution? What if you want (string | number)[] instead of string[] | number[]? You prevent distribution by wrapping T in a tuple:
type ToArrayNoDist<T> =
[T] extends [any] ? T[] : never;
type Result =
ToArrayNoDist<string | number>;
// (string | number)[]
Because T is wrapped in [T], it is no longer "naked." TypeScript treats the entire union as a single unit and does not distribute. This simple trick gives you full control over distribution!
Conditional types have some tricky behaviors that catch developers off guard. Let's walk through the most common traps at this Begumpet flyover so you don't take the wrong diversion!
Trap 1: Unintended Distribution. Forgetting that distribution happens automatically with naked type parameters is the #1 mistake. You might expect a unified object, but you get a union of objects:
type Wrap<T> =
T extends any ? { value: T } : never;
type R = Wrap<string | number>;
// { value: string } | { value: number }
// NOT { value: string | number }!
If you want { value: string | number }, prevent distribution using [T] extends [any].
Trap 2: Non-Generics Are Immediate. Conditional types with non-generic types are evaluated immediately. There's no magic deferral:
type X =
string extends number
? "yes" : "no";
// X is always "no", no suspense
Trap 3: T extends any Always True. T extends any ? X : Y means the false branch is unreachable because everything extends any. This is used intentionally to force distribution, but can confuse beginners who wonder why the false branch exists.
Trap 4: The "any" Anomaly. Checking any against a condition produces BOTH branches unioned together:
type Weird =
any extends string
? "yes" : "no";
// Result is "yes" | "no" !
Because any is special in TypeScript — it can be anything, so it distributes and satisfies both the true and false branches simultaneously. Be very careful when any might flow into your conditional types!
Trap 5: Never in Unions. Distributing over never yields never. Since never is an empty union, there are no members to iterate over:
type ToArray<T> =
T extends any ? T[] : never;
type R = ToArray<never>;
// never (NOT never[])
This makes logical sense — an empty union mapped gives an empty union, which is never.
Here's your complete cheatsheet for conditional types — the Begumpet flyover handbook. Pin this to your desk, bhai!
Basic Syntax:
// Type-level if-statement
type Result =
T extends U ? X : Y;
Built-in Utilities:
Exclude<T, U>
// Removes U from T
Extract<T, U>
// Keeps only U from T
NonNullable<T>
// Removes null | undefined
Distribution Control:
// Naked T: DISTRIBUTES
type Dist<T> =
T extends U ? X : Y;
// Wrapped T: NO distribution
type NoDist<T> =
[T] extends [U] ? X : Y;
Key Rules to Remember:
- Rule 1: Conditional types are type-level if-statements — they choose types based on conditions.
- Rule 2: Distribution is automatic for naked type params — T is split into individual union members.
- Rule 3: Use
[T] extends [U]to prevent distribution and treat the union as one. - Rule 4:
neverdistributes tonever(empty union maps to empty union). - Rule 5:
anydistributes to both branches, resulting in a union of true and false outcomes.
The golden rule: "Conditional types are the Begumpet flyover — each vehicle type takes its own route. Distribution means one cop per vehicle, not one rule for all. Understand the jugaad of distribution, bhai!"
Key Points
- Conditional types are type-level if-statements: T extends U ? X : Y
- Distribution happens automatically when T is a naked type parameter
- Distribution splits union members and evaluates each individually
- Wrap T in a tuple [T] extends [U] to prevent distribution
- never distributes to never because it is an empty union
- any in conditional types produces a union of both branches
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login