0
0
Typescriptprogramming~15 mins

Non-distributive conditional types in Typescript - Deep Dive

Choose your learning style9 modes available
Overview - Non-distributive conditional types
What is it?
Non-distributive conditional types in TypeScript are conditional types that do not automatically split or distribute over union types. Normally, conditional types in TypeScript distribute over each member of a union, but non-distributive conditional types prevent this behavior, allowing you to treat the union as a whole. This helps when you want to apply a condition to the entire union type rather than each part separately.
Why it matters
Without non-distributive conditional types, TypeScript would always break down unions and apply conditions to each member, which can lead to unexpected or unwanted results. This makes it hard to write precise type logic when you want to handle unions as a single entity. Non-distributive conditional types solve this by giving you control over how conditions apply, improving type safety and expressiveness in complex type scenarios.
Where it fits
Before learning non-distributive conditional types, you should understand basic TypeScript types, union types, and how conditional types work with distribution. After mastering this, you can explore advanced type manipulations like mapped types, template literal types, and recursive conditional types.
Mental Model
Core Idea
Non-distributive conditional types treat a union type as a single whole in a condition, instead of splitting it into parts and applying the condition to each part separately.
Think of it like...
Imagine you have a basket of mixed fruits (a union). Normally, you check each fruit one by one to see if it's an apple (distributive). Non-distributive conditional types are like checking the whole basket at once to see if it contains only apples or not, without looking at each fruit separately.
Union Type (A | B | C)
  │
  ├─ Distributive Conditional Type: Applies condition to A, B, and C separately
  │    ├─ Condition on A
  │    ├─ Condition on B
  │    └─ Condition on C
  │
  └─ Non-distributive Conditional Type: Applies condition to (A | B | C) as a whole
       └─ Condition on entire union
Build-Up - 7 Steps
1
FoundationUnderstanding union types basics
🤔
Concept: Learn what union types are and how TypeScript treats them.
In TypeScript, a union type means a value can be one of several types. For example, type Fruit = 'apple' | 'banana' means a variable of type Fruit can be either 'apple' or 'banana'. This lets you write flexible code that accepts multiple types.
Result
You can declare variables that accept multiple types using the | symbol.
Knowing union types is essential because conditional types often operate on unions by default.
2
FoundationConditional types and distribution
🤔
Concept: Conditional types automatically distribute over union types by default.
A conditional type looks like T extends U ? X : Y. When T is a union, TypeScript applies the condition to each member separately. For example, if T is A | B, then the conditional type becomes (A extends U ? X : Y) | (B extends U ? X : Y).
Result
Conditional types split unions into parts and apply the condition to each part.
Understanding this default distribution behavior is key to seeing why non-distributive conditional types are needed.
3
IntermediateWhy distribution can cause problems
🤔Before reading on: do you think conditional types always behave as expected with unions? Commit to yes or no.
Concept: Distribution can lead to unexpected or undesired type results when you want to treat the union as a whole.
Sometimes you want to check a property or condition on the entire union type, not each member separately. For example, if you want to check if a type is exactly a union or not, distribution breaks this logic by splitting it.
Result
Distribution can cause conditional types to produce unions of results instead of a single unified result.
Knowing when distribution breaks your logic helps you understand the need for non-distributive conditional types.
4
IntermediateHow to prevent distribution
🤔Before reading on: do you think wrapping a type in a tuple affects conditional type distribution? Commit to yes or no.
Concept: Wrapping a type in a single-element tuple prevents distribution in conditional types.
TypeScript distributes conditional types only when the checked type is a naked type parameter. Wrapping it in a tuple like [T] stops distribution. For example: type NonDist = [T] extends [U] ? X : Y applies the condition to T as a whole.
Result
You can control distribution by wrapping types in tuples.
Understanding this trick gives you precise control over conditional type behavior.
5
IntermediateWriting non-distributive conditional types
🤔
Concept: Use tuple wrapping to write conditional types that treat unions as a whole.
Example: type IsString = [T] extends [string] ? true : false; IsString<'a' | 'b'> evaluates to false because the whole union is not assignable to string as a single type, unlike distributive conditional types which would check each member.
Result
Conditional checks apply to the entire union, not each member.
This pattern is the foundation for non-distributive conditional types.
6
AdvancedCombining distributive and non-distributive types
🤔Before reading on: can you mix distributive and non-distributive conditional types in one expression? Commit to yes or no.
Concept: You can combine both behaviors to create complex type logic by selectively wrapping types in tuples.
Example: type Conditional = T extends string ? 'string' : [T] extends [number] ? 'number' : 'other'; Here, the first condition distributes, but the second is non-distributive due to tuple wrapping.
Result
You get fine-grained control over how conditions apply to unions.
Knowing how to mix these behaviors lets you write powerful and precise type utilities.
7
ExpertSurprising behavior with never and unknown
🤔Before reading on: does wrapping never or unknown in a tuple affect conditional type distribution? Commit to yes or no.
Concept: Special types like never and unknown behave differently with distribution and tuple wrapping, leading to subtle effects.
For example, never distributes to nothing, but [never] is a tuple containing never, so it does not distribute. Similarly, unknown behaves differently in conditional types depending on distribution. These subtleties can cause unexpected type results if not understood.
Result
You must carefully handle never and unknown when using non-distributive conditional types.
Understanding these edge cases prevents subtle bugs in advanced type programming.
Under the Hood
TypeScript's conditional types distribute over unions by internally iterating over each union member and applying the condition separately. This happens only when the checked type is a naked type parameter. Wrapping the type in a tuple changes the checked type to a single-element tuple, which is not a naked type parameter, so distribution does not occur. This is a design choice in the compiler's type system to allow control over distribution behavior.
Why designed this way?
Distribution by default simplifies many common type operations by automatically handling unions member-wise. However, this default behavior can be limiting when you want to treat unions as a whole. The tuple wrapping trick was introduced as a simple, backward-compatible way to disable distribution without adding new syntax or complexity to the language.
┌─────────────────────────────┐
│ Conditional Type Check       │
│                             │
│  Is T a naked type parameter? ── Yes ──▶ Distribute over union members
│                             │
│                             No
│                             │
│  Treat T as a single whole  │
└─────────────┬───────────────┘
              │
              ▼
       Apply condition once
Myth Busters - 4 Common Misconceptions
Quick: Do conditional types always distribute over unions, no exceptions? Commit to yes or no.
Common Belief:Conditional types always distribute over union types in TypeScript.
Tap to reveal reality
Reality:Conditional types distribute only when the checked type is a naked type parameter; wrapping the type in a tuple disables distribution.
Why it matters:Assuming unconditional distribution leads to incorrect type logic and bugs when you want to treat unions as a whole.
Quick: Does wrapping a type in a tuple change the type itself? Commit to yes or no.
Common Belief:Wrapping a type in a tuple changes the type's meaning or structure significantly.
Tap to reveal reality
Reality:Wrapping in a tuple is a compile-time trick that only affects conditional type distribution behavior; it does not change the underlying type's runtime meaning.
Why it matters:Misunderstanding this can cause confusion about type transformations and lead to unnecessary complexity.
Quick: Is non-distributive conditional typing a new feature added explicitly in TypeScript? Commit to yes or no.
Common Belief:Non-distributive conditional types are a new, separate feature with special syntax.
Tap to reveal reality
Reality:Non-distributive conditional types are achieved by a clever use of existing tuple wrapping syntax, not a separate language feature.
Why it matters:Knowing this helps you understand the language's design philosophy and how to leverage existing features creatively.
Quick: Does the never type always behave the same in distributive and non-distributive conditional types? Commit to yes or no.
Common Belief:never behaves the same regardless of distribution in conditional types.
Tap to reveal reality
Reality:never distributes to nothing in distributive conditional types but behaves differently when wrapped in tuples, affecting type results.
Why it matters:Ignoring this can cause subtle bugs in complex type computations involving never.
Expert Zone
1
Tuple wrapping disables distribution only for the checked type, but distribution still occurs if the type appears elsewhere naked in the conditional type.
2
Non-distributive conditional types can be combined with infer keyword to extract complex type information without unwanted distribution.
3
Some utility types in popular libraries use non-distributive conditional types internally to ensure precise behavior with unions.
When NOT to use
Avoid non-distributive conditional types when you want to apply logic to each member of a union separately, such as filtering or mapping over union members. Use distributive conditional types in those cases for simpler and more intuitive results.
Production Patterns
In production, non-distributive conditional types are used to create precise type guards, exact type checks, and to build advanced utility types that require treating unions as single entities, such as deep equality checks or conditional property extraction.
Connections
Set theory
Non-distributive conditional types treat unions like whole sets rather than individual elements.
Understanding how sets can be treated as wholes or as collections of elements helps grasp why distribution matters in type logic.
Functional programming - map vs fold
Distributive conditional types are like map (apply function to each element), non-distributive types are like fold (reduce whole structure).
This analogy clarifies the difference between applying logic element-wise versus to the entire structure.
Legal contracts
Treating a union type as a whole is like interpreting a contract clause as a single statement rather than breaking it into parts.
This shows how context changes interpretation, similar to how tuple wrapping changes type evaluation.
Common Pitfalls
#1Expecting conditional types to always distribute over unions.
Wrong approach:type Result = T extends string ? true : false; // For T = 'a' | number, Result becomes true | false
Correct approach:type Result = [T] extends [string] ? true : false; // For T = 'a' | number, Result becomes false
Root cause:Not realizing that conditional types distribute only when the checked type is naked, leading to unexpected union results.
#2Wrapping the entire conditional type in a tuple instead of the checked type.
Wrong approach:type Result = ([T] extends [string] ? true : false)[]; // This wraps the whole conditional type in an array, not preventing distribution.
Correct approach:type Result = [T] extends [string] ? true : false; // Correctly prevents distribution by wrapping T only.
Root cause:Misunderstanding where to apply tuple wrapping to control distribution.
#3Ignoring special behavior of never in conditional types.
Wrong approach:type Result = T extends never ? true : false; // For T = never, Result is never, not true or false.
Correct approach:type Result = [T] extends [never] ? true : false; // For T = never, Result is true.
Root cause:Not accounting for how never distributes to no members, causing unexpected type results.
Key Takeaways
Conditional types in TypeScript distribute over union types by default, applying conditions to each member separately.
Non-distributive conditional types prevent this behavior by wrapping the checked type in a tuple, treating the union as a whole.
This control over distribution is essential for writing precise and predictable type logic with unions.
Special types like never and unknown have unique behaviors with distribution and require careful handling.
Mastering non-distributive conditional types unlocks advanced type programming and helps avoid subtle bugs.