Non-distributive conditional types in Typescript - Time & Space Complexity
We want to understand how the time it takes to evaluate a non-distributive conditional type changes as the input type grows.
Specifically, how does TypeScript handle conditional checks when the input is a union type but the condition is made non-distributive?
Analyze the time complexity of this TypeScript code snippet.
type NonDistributive = [T] extends [string | number] ? true : false;
// Example usage:
type Test1 = NonDistributive;
type Test2 = NonDistributive;
This code checks if a type T is assignable to string or number without distributing over unions.
Look for repeated checks or evaluations inside the conditional type.
- Primary operation: TypeScript evaluates the conditional once on the whole type wrapped in a tuple to prevent distribution.
- How many times: Exactly once, regardless of how many union members T has.
Since the conditional is non-distributive, the check happens once no matter how many types are in the union.
| Input Size (n) | Approx. Operations |
|---|---|
| 1 (e.g., string) | 1 |
| 2 (e.g., string | number) | 1 |
| 10 (e.g., union of 10 types) | 1 |
Pattern observation: The number of operations stays the same even if the union grows larger.
Time Complexity: O(1)
This means the evaluation time does not increase as the input type union grows.
[X] Wrong: "The conditional type runs once for each member of the union, so time grows with union size."
[OK] Correct: Wrapping the type in a tuple disables distribution, so the check happens only once on the whole union.
Understanding how TypeScript handles conditional types helps you write efficient type checks and reason about compiler performance in real projects.
What if we removed the tuple wrapping and let the conditional distribute over the union? How would the time complexity change?
Practice
Solution
Step 1: Understand distributive conditional types
Distributive conditional types apply the condition to each member of a union type separately.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.Final Answer:
It evaluates the condition on the entire union type as a whole, not on each member separately. -> Option DQuick Check:
Non-distributive = whole union check [OK]
- Thinking conditional always distributes over unions
- Confusing non-distributive with type narrowing
- Assuming it only works on primitives
Solution
Step 1: Recognize non-distributive syntax
Wrapping the type parameter in a tuple, like [T], prevents distribution over unions.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.Final Answer:
type NonDist<T> = [T] extends [string] ? true : false; -> Option CQuick Check:
Tuple wrapping disables distribution [OK]
- Not wrapping type in tuple, causing distribution
- Using invalid conditional syntax
- Confusing union with intersection in condition
type Test<T> = T extends string ? 'yes' : 'no'; type Result = Test<'a' | number>;
What is the type of
Result?Solution
Step 1: Understand distributive conditional types
SinceTis a union'a' | number, the condition distributes over each member.Step 2: Evaluate each member
'a' extends string is true, so 'yes'; number extends string is false, so 'no'. Combined result is 'yes' | 'no'.Final Answer:
'yes' | 'no' -> Option AQuick Check:
Distributes over union = 'yes' | 'no' [OK]
- Assuming whole union checked at once
- Expecting only 'yes' or only 'no'
- Confusing union with intersection
type NonDist<T> = T extends [string] ? true : false;
Solution
Step 1: Analyze the condition
The condition checks ifTextends[string], a tuple type, not wrappingT.Step 2: Understand distribution prevention
To prevent distribution,Titself must be wrapped in a tuple:[T] extends [string]. This code does not do that, so distribution is not prevented.Final Answer:
It incorrectly checks if T extends a tuple containing string, not preventing distribution. -> Option AQuick Check:
Wrap T, not string, to prevent distribution [OK]
- Wrapping the wrong side of extends
- Confusing tuple with type wrapper
- Assuming syntax error instead of logic error
T is exactly string | number (no more, no less)?Solution
Step 1: Prevent distribution with tuple wrapping
WrappingTin a tuple[T]prevents distribution over unions.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.Final Answer:
type Exact<T> = [T] extends [string | number] ? ([string | number] extends [T] ? true : false) : false; -> Option BQuick Check:
Tuple wrap + mutual extends = exact type check [OK]
- Not preventing distribution, causing partial matches
- Checking only one extends direction
- Using incorrect tuple wrapping
Ready to prove it?
Pass the test and this pattern moves unknown
