Siddhant DevalAuthor
Senior Full-Stack Engineer·Aug 27, 2026·14 min read
Conditional Types & `infer`: Type-Level Control Flow
Conditional types are if-else at the type level. `infer` is a pattern-match variable binding. Together they enable type-level algorithms — and power every major TypeScript library from tRPC to Prisma to Zod.
Technical Series
TypeScript Mastery
Part 5 of 11
Conditional Types & infer: Type-Level Control Flow
Types are a specification language — not an annotation layer. The most striking proof of that is conditional types: TypeScript has
if-else at the type level. T extends U ? X : Y is not a curiosity. It is the implementation language for ReturnType<T>, Awaited<T>, Parameters<T>, and every complex type utility in every serious TypeScript library. Understanding it is the difference between consuming those utilities and being able to write them.1. Conditional Type Syntax
The conditional type form
T extends U ? X : Y reads as: "If T is assignable to U, produce X; otherwise produce Y." This evaluation happens entirely at the type level, at compile time.typescript
2. Distributive Conditional Types
When the checked type
T is a naked type parameter (not wrapped), the conditional distributes over each union member:typescript
This is the mechanism that makes
Exclude<T, U> work:typescript
![Three-column diagram. Left column: 'Input: string | number'. Center column: 'Conditional: T extends any ? T[] : never' with a fork splitting into two paths labeled 'T = string' and 'T = number'. Right column: 'Result: string[] | number[]' — two boxes joined by a union pipe. An annotation below reads: 'Distribution happens member-by-member — each union member is evaluated independently'. Caption: 'Distributive conditional types evaluate each union member separately when T is a naked type parameter in the extends position'.](/assets/blog/frontend/typescript-conditional-types-infer/fig-01.png)
Expand
3. Disabling Distribution with Tuple Wrapping
Distribution is sometimes undesirable. Wrap both sides in a tuple to compare the union as a whole:
typescript
Crucial Requirement
[T] extends [never] is the canonical way to check if T is exactly never. The naked form T extends never ? true : false returns never when T = never because distribution over the empty union produces no branches at all, yielding never.4. infer — Pattern-Match Variable Binding
infer introduces a type variable inside the extends clause that is bound to the matched shape and available in the true branch:typescript
![Two-row diagram. Row 1 labeled 'Input type': function signature (x: number) => string drawn as a box with labeled slots. Row 2 labeled 'Pattern: T extends (...args: any[]) => infer R': the pattern is overlaid on the input, with a red highlight on the return position labeled 'infer R — binds R = string'. A right arrow from the highlighted slot points to a box labeled 'True branch: R = string'. Below: annotation 'infer creates a new type variable bound to the matched position — only valid in the extends clause'. Caption: 'infer is a pattern-match binding — it names the type at a specific structural position and makes it available in the true branch'.](/assets/blog/frontend/typescript-conditional-types-infer/fig-02.png)
Expand
5. Classic infer Patterns
5.1 Parameters<T> — Extracting a Full Parameter Tuple
typescript
5.2 Awaited<T> — Unwrapping Nested Promises
typescript
5.3 infer with Template Literals — Extracting String Segments
typescript
6. Recursive Conditional Types — Performance Considerations
Recursive conditional types can be powerful but expensive:
typescript
Performance / Safety Warning
TypeScript enforces a recursion depth limit (approximately 100 levels for conditional types). Recursive types that don't terminate quickly will cause a "Type instantiation is excessively deep" error. Always define a clear base case, and be wary of recursive utilities applied to user-provided types of unknown depth.

Expand
Summary
| Concept | Rule |
|---|---|
T extends U ? X : Y | Type-level if-else — evaluates at compile time based on assignability |
| Distribution | Naked type parameters distribute over unions automatically — each member is evaluated independently |
[T] extends [U] | Tuple wrapping suppresses distribution — the union is compared atomically |
infer R | Binds a type variable to the matched position in the extends clause |
infer constraint | infer only works in the extends position — never in the true/false branches |
| Recursive depth | TypeScript limits recursion to ~100 levels — always define a clear base case |
IsNever<T> | Use [T] extends [never] ? true : false — the naked form distributes incorrectly |
What's Next
In Part 6, we cover Template Literal Types — the mechanism that makes TypeScript aware of string structure. Combined withinfer, they enable type-safe route contracts, event handler maps, and CSS property validation with zero runtime overhead.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#TypeScript#Conditional Types#infer#Advanced TypeScript#Type-Level Programming