ProgrammingTypeScript Architect

How do conditional types work in TypeScript? What is the distributive behavior of conditional types and how do you work with it? Explain with examples.

Pass interviews with Hintsage AI assistant

Answer.

Conditional Types in TypeScript allow you to describe one type based on another using the principle T extends U ? X : Y. Such types provide powerful capabilities when building complex type logic, especially in libraries and API declarations.

Example of a basic conditional type:

type IsString<T> = T extends string ? 'yes' : 'no'; type A = IsString<string>; // 'yes' type B = IsString<number>; // 'no'

Distributive Behavior

When a conditional type is applied to a union type, TypeScript "distributes" the condition over each element of the union separately. This is called the distributive behavior of conditional types.

Example:

type Foo<T> = T extends { id: number } ? string : boolean; type Result = Foo<{id: number} | {name: string}>; // string | boolean

This is a very powerful feature, but it can lead to confusion regarding the expected result, especially when working with arrays and type mapping.

How to avoid distributive behavior

Wrap the type in a tuple:

type NoDistrib<T> = [T] extends [{id: number}] ? string : boolean; type Result = NoDistrib<{id: number} | {name: string}>; // boolean

Trick Question.

Question: "What happens if you use a conditional type with union types without wrapping them in tuples? Is the result always the same as under normal logic?"

Answer: The result can be unexpected! Due to distributivity, the condition is applied to each member of the union types separately. To strictly compare the entire union type, you need to use a wrapper (tuple).

Example:

type Test<T> = T extends string ? number : boolean; type A = Test<string | boolean>; // number | boolean, not just boolean

Examples of real mistakes due to lack of knowledge of the topic nuances.


Story

In a serialization library, a conditional type was used to check the structure of data, but they forgot to wrap the generic parameter in a tuple. As a result, on complex union types, the declarations broke, and the compiler produced unpredictable types when using the API.


Story

When trying to implement type transformation for processing model fields, part of the information was lost: due to distributivity creating unions, a couple of branches of logic were forgotten, and ultimately the typing became too permissive.


Story

A developer assumed that T extends SomeType inside conditional types would behave as expected for the entire object, but "spraying" happened — the compiler pointed out inconsistencies, leading to type chaos and serious bugs in auto-documentation generation.