🔁 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 to U, then the type resolves to X; otherwise, it resolves to Y.

🧪 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: Does string extend string? Yes → returns "Yes"
  • IsString<number>: number does NOT extend string → 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 type R of the function T.
  • If T matches a function pattern, we extract R; otherwise, fallback to never.

✅ 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>, and NonNullable<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 :

Leave a Reply

Your email address will not be published. Required fields are marked *

Share

TypeScript — Conditional Types

Or Copy Link

CONTENTS
Scroll to Top