🔁 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
T
is 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: Doesstring
extendstring
? Yes → returns"Yes"
IsString<number>
:number
does 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 R
lets us infer the return typeR
of the functionT
.- If
T
matches 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,
T
is 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 :