9️⃣ 🧬TypeScript Type Manipulation & Utility Types
Estimated reading: 4 minutes 301 views

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 :
Share

TypeScript — Conditional Types

Or Copy Link

CONTENTS
Scroll to Top