TypeScript – Conditional Types Explained with Real-World Examples
Introduction – What Are Conditional Types in TypeScript?
In modern TypeScript, Conditional Types allow you to define types based on conditions — just like how if/else works in programming logic, but at the type level. This powerful feature adds flexibility, reuse, and intelligence to your type declarations, enabling dynamic type transformations and decision-making.
In this guide, you’ll learn:
- The syntax of conditional types
- How conditional types work internally
- Practical use cases and type transformations
- Advanced patterns with generics and inference
What Are Conditional Types?
Conditional types use a ternary-like syntax to determine types based on whether a condition is true or false.
Syntax:
T extends U ? X : Y
- If
Tis assignable toU, then the type resolves toX; otherwise, it resolves toY.
Example 1: Basic Conditional Type
type IsString<T> = T extends string ? "Yes" : "No";
type A = IsString<string>; // "Yes"
type B = IsString<number>; // "No"
Explanation:
IsString<string>checks: Doesstringextendstring? Yes → returns"Yes"IsString<number>:numberdoes NOT extendstring→ returns"No"
Example 2: Conditional Return Type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ExampleFn = (a: number) => boolean;
type Result = ReturnType<ExampleFn>; // boolean
Explanation:
infer Rlets us infer the return typeRof the functionT.- If
Tmatches a function pattern, we extractR; otherwise, fallback tonever.
This is exactly how TypeScript’s built-in ReturnType<T> utility works.
Example 3: Conditional Types with Unions (Distributive)
type IsNumber<T> = T extends number ? "number" : "not number";
type Result = IsNumber<number | string>;
// "number" | "not number"
Explanation:
Conditional types distribute over unions by default, applying the condition independently to each type in the union:
number → "number"string → "not number"
Result:"number" | "not number"
Controlling Distribution Using Wrapper
type NoDistribute<T> = [T] extends [number] ? "Yes" : "No";
type Result = NoDistribute<number | string>; // "No"
Explanation:
- Wrapping the type in square brackets prevents TypeScript from distributing over the union.
- Now,
Tis treated as a whole rather than individually.
Example 4: Building a Utility Type
Let’s create a Truthy<T> type that filters out false, null, and undefined.
type Truthy<T> = T extends false | null | undefined ? never : T;
type A = Truthy<string>; // string
type B = Truthy<undefined>; // never
type C = Truthy<false | "hi">; // "hi"
This can help build form validations, data sanitizers, and type-safe filters.
Example 5: Replace Types with Conditional Logic
type ReplaceBoolean<T> = T extends boolean ? "replaced" : T;
type A = ReplaceBoolean<string>; // string
type B = ReplaceBoolean<true>; // "replaced"
You can use this approach to remap or exclude certain types from unions.
Example 6: Combine Conditional + Mapped Types
type Options = {
darkMode: boolean;
fontSize: number;
fontFamily: string;
};
type SettingsPanel = {
[K in keyof Options]: Options[K] extends boolean ? "toggle" : "input"
};
// Result:
// {
// darkMode: "toggle";
// fontSize: "input";
// fontFamily: "input";
// }
Conditional types within mapped types allow dynamic component rendering, UI logic, and form generation — all at type level!
Advanced: Conditional Types with Inference
type GetFirstArg<T> = T extends (arg: infer A, ...args: any[]) => any ? A : never;
type Fn = (x: number, y: string) => void;
type Arg1 = GetFirstArg<Fn>; // number
infer keyword extracts part of a type — like parameters or return types — and gives it a temporary name you can use within the condition.
Summary – Recap & Real-World Relevance
Conditional types allow you to express branching logic at the type level, enabling more intelligent, responsive, and reusable type definitions.
Key Takeaways:
- Syntax:
T extends U ? X : Y - Built-in utilities like
ReturnType<T>,Exclude<T, U>, andNonNullable<T>rely on conditional types - Works seamlessly with
infer, mapped types, and generics - Distributes over unions automatically (can be disabled)
Real-World Applications:
- Type-safe API client wrappers
- Form generators
- Smart components based on props
- Creating custom utility types for better DX (developer experience)
FAQs
Do conditional types distribute over union types?
Yes, they distribute by default. To prevent that, wrap the type in square brackets like [T].
What is infer used for in conditional types?
infer is used to capture part of a type (like the return value or argument) inside a conditional type for reuse.
Can conditional types be nested?
Absolutely. You can nest conditional types just like if/else if/else statements.
type Nested<T> = T extends string
? "text"
: T extends number
? "number"
: "unknown";
Are conditional types used in utility types?
Yes, built-in types like:
ReturnType<T>Extract<T, U>Exclude<T, U>NonNullable<T>
…all use conditional types internally.
Share Now :
