Chapter 27: TypeScript Conditional Types

Conditional Types in TypeScript like we’re sitting together in a calm room, drawing on a whiteboard, slowly building understanding from simple to powerful real patterns.

Conditional types are one of the most important building blocks of advanced TypeScript. They appeared in TypeScript 2.8 (2018) and quickly became the foundation for almost every sophisticated type utility you see today (including most built-in Utility Types like Extract, Exclude, NonNullable, ReturnType, etc.).

1. The absolute simplest mental model

Think of conditional types as the ternary operator (? 🙂 but for types.

JavaScript:

JavaScript

TypeScript (conditional type):

TypeScript

But the condition is not a boolean value — it’s a type relationship test using the keyword extends.

TypeScript

2. Basic syntax & meaning

TypeScript
TypeScript

Key insight #1 extends here means “is assignable to” (structural compatibility), not strict equality.

TypeScript

3. The real power — infer keyword

infer lets you declare a type variable inside the conditional and capture part of the input type.

Classic built-in example — ReturnType (TypeScript copies this pattern)

TypeScript

Very common real usage — unwrap Promise(s) recursively

TypeScript

4. Distributive conditional types (the tricky but powerful behavior)

When the checked type is a union, conditional types become distributive (they apply to each member separately).

TypeScript

Very useful pattern — filter union members

TypeScript

(This is exactly how built-in Extract<T, U> works)

5. Realistic production patterns in 2025–2026

Pattern 1: Function overload helper (very common in libraries)

TypeScript

Pattern 2: Deep partial / mutable / readonly helpers

TypeScript

Pattern 3: If / Unless type (very clean control flow at type level)

TypeScript

Pattern 4: API shape based on method (very frequent in SDKs / tRPC-like code)

TypeScript

Pattern 5: Exclude nullish + unwrap (real API / form handling)

TypeScript

6. Quick cheat-sheet table (keep in your notes)

Pattern / Goal Typical Conditional Type syntax Since TS version Very common in…
Basic yes/no decision T extends U ? X : Y 2.8 Almost everywhere
Extract inner type T extends Something<infer Inner> ? Inner : never 2.8 Promise, Array, ReturnType, etc.
Recursive unwrap Nested infer + self-reference 2.8+ Awaited, DeepPartial, DeepReadonly
Distributive filter T extends U ? T : never (applied to union) 2.8 Extract, Exclude, NonNullable
Different output based on literal T extends “A” ? TypeA : T extends “B” ? TypeB : never 2.8 Discriminated unions, variants
Combine with mapped types { [K in keyof T]: T[K] extends string ? number : T[K] } 2.8+ Transform object shapes

7. Common gotchas / things that surprise people

  • Unions are distributed → sometimes you need [T] extends [U] trick to prevent distribution
  • never in false branch is very common (acts like filter)
  • Deep recursion can hit type instantiation limit (error TS2589) — increase with –maxNodeModuleJsDepth or simplify
  • infer only works in extends clause (not in true/false branches directly)

Your mini homework (try in playground right now)

  1. Write your own MyAwaited<T> that unwraps nested Promises
  2. Create OnlyFunctions<T> that keeps only function members of an object type
  3. Build IfEquals<A, B, Yes, No> that returns Yes only if A === B (literal equality)

Which pattern feels most useful or most confusing for you right now?

Want to:

  • See how conditional types power most built-in utility types
  • Deep dive into distributive behavior with tricks to disable it
  • Build a small type-safe router / path params extractor
  • Compare conditional types vs satisfies vs generics constraints

Just tell me — we’ll continue from exactly there! 😄

You may also like...

Leave a Reply

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