Bird
Raised Fist0
Typescriptprogramming~15 mins

Non-distributive conditional types in Typescript - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What does a non-distributive conditional type in TypeScript do differently compared to a distributive conditional type?
easy
A. It converts all types to string before evaluation.
B. It always distributes the condition over each member of the union type.
C. It only works with primitive types, not unions.
D. It evaluates the condition on the entire union type as a whole, not on each member separately.

Solution

  1. Step 1: Understand distributive conditional types

    Distributive conditional types apply the condition to each member of a union type separately.
  2. Step 2: Understand non-distributive conditional types

    Non-distributive conditional types evaluate the condition on the entire union type as a single entity, not distributing over members.
  3. Final Answer:

    It evaluates the condition on the entire union type as a whole, not on each member separately. -> Option D
  4. Quick Check:

    Non-distributive = whole union check [OK]
Hint: Non-distributive means no splitting union, check whole type [OK]
Common Mistakes:
  • Thinking conditional always distributes over unions
  • Confusing non-distributive with type narrowing
  • Assuming it only works on primitives
2. Which syntax correctly defines a non-distributive conditional type in TypeScript?
easy
A. type NonDist<T> = T | string ? true : false;
B. type NonDist<T> = T extends string ? true : false;
C. type NonDist<T> = [T] extends [string] ? true : false;
D. type NonDist<T> = T & string ? true : false;

Solution

  1. Step 1: Recognize non-distributive syntax

    Wrapping the type parameter in a tuple, like [T], prevents distribution over unions.
  2. Step 2: Compare options

    type NonDist<T> = [T] extends [string] ? true : false; uses [T] extends [string], which is the correct non-distributive syntax. type NonDist<T> = T extends string ? true : false; distributes over unions. Options C and D are invalid conditional syntax.
  3. Final Answer:

    type NonDist<T> = [T] extends [string] ? true : false; -> Option C
  4. Quick Check:

    Tuple wrapping disables distribution [OK]
Hint: Wrap type in tuple [T] to prevent distribution [OK]
Common Mistakes:
  • Not wrapping type in tuple, causing distribution
  • Using invalid conditional syntax
  • Confusing union with intersection in condition
3. Given the code:
type Test<T> = T extends string ? 'yes' : 'no';
type Result = Test<'a' | number>;

What is the type of Result?
medium
A. 'yes' | 'no'
B. 'yes'
C. 'no'
D. Error

Solution

  1. Step 1: Understand distributive conditional types

    Since T is a union 'a' | number, the condition distributes over each member.
  2. Step 2: Evaluate each member

    'a' extends string is true, so 'yes'; number extends string is false, so 'no'. Combined result is 'yes' | 'no'.
  3. Final Answer:

    'yes' | 'no' -> Option A
  4. Quick Check:

    Distributes over union = 'yes' | 'no' [OK]
Hint: Conditional distributes over union unless wrapped [OK]
Common Mistakes:
  • Assuming whole union checked at once
  • Expecting only 'yes' or only 'no'
  • Confusing union with intersection
4. Identify the error in this non-distributive conditional type:
type NonDist<T> = T extends [string] ? true : false;
medium
A. It incorrectly checks if T extends a tuple containing string, not preventing distribution.
B. It correctly prevents distribution by wrapping T in a tuple.
C. It causes a syntax error because of missing brackets.
D. It always returns false regardless of T.

Solution

  1. Step 1: Analyze the condition

    The condition checks if T extends [string], a tuple type, not wrapping T.
  2. Step 2: Understand distribution prevention

    To prevent distribution, T itself must be wrapped in a tuple: [T] extends [string]. This code does not do that, so distribution is not prevented.
  3. Final Answer:

    It incorrectly checks if T extends a tuple containing string, not preventing distribution. -> Option A
  4. Quick Check:

    Wrap T, not string, to prevent distribution [OK]
Hint: Wrap T in tuple, not the type to check [OK]
Common Mistakes:
  • Wrapping the wrong side of extends
  • Confusing tuple with type wrapper
  • Assuming syntax error instead of logic error
5. How can you write a non-distributive conditional type that checks if a union type T is exactly string | number (no more, no less)?
hard
A. type Exact<T> = T extends string | number ? true : false;
B. type Exact<T> = [T] extends [string | number] ? ([string | number] extends [T] ? true : false) : false;
C. type Exact<T> = T extends [string | number] ? true : false;
D. type Exact<T> = [T] extends [string] | [number] ? true : false;

Solution

  1. Step 1: Prevent distribution with tuple wrapping

    Wrapping T in a tuple [T] prevents distribution over unions.
  2. Step 2: Use mutual assignability checks

    Check [T] extends [string | number] and [string | number] extends [T] to ensure exact match, no extra or missing types.
  3. Final Answer:

    type Exact<T> = [T] extends [string | number] ? ([string | number] extends [T] ? true : false) : false; -> Option B
  4. Quick Check:

    Tuple wrap + mutual extends = exact type check [OK]
Hint: Wrap T and check both extends directions for exact match [OK]
Common Mistakes:
  • Not preventing distribution, causing partial matches
  • Checking only one extends direction
  • Using incorrect tuple wrapping
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard