1. The Problem: Unwanted Distribution#
Let’s revisit the ToArray example from the previous episode.
type ToArray<T> = T extends any ? T[] : never;
// Distributes into: string[] | number[]
type Distributed = ToArray<string | number>;
What if we don’t want string[] | number[]? What if we are trying to construct a single array that can hold a mix of strings and numbers: (string | number)[]?
Because T is a “naked” generic parameter in the extends clause, TypeScript forcefully distributes it.
2. The Solution: Tuple Wrapping [T]#
To disable distribution, you must ensure the generic type parameter is no longer “naked”. The standard, idiomatic way to achieve this in TypeScript is to wrap both sides of the extends clause in a tuple (brackets []).
// 🟢 Non-Distributive Conditional Type
type NonDistributiveToArray<T> = [T] extends [any] ? T[] : never;
// Evaluates the union as a single block!
type UnifiedArray = NonDistributiveToArray<string | number>;
/* Inferred Type:
(string | number)[]
*/By wrapping T in brackets, we are telling the compiler: “Do not evaluate the individual elements of this union. Evaluate the union as a single structural tuple [string | number].”
Because [string | number] extends [any], the condition resolves to true, and it returns T[] (where T is still intact as the full union).
3. Real-World Mastery: Building IsUnion<T>#
A classic advanced TypeScript interview question is:
“Write a generic type that returns
trueifTis a union, andfalseotherwise.”
You can solve this elegantly by intentionally pitting the distributive behavior of T against the non-distributive behavior of T!
// 1. T extends any (Distributive step: Splits the union into individual types)
// 2. [U] extends [T] (Non-distributive step: Compares the original full union against the distributed piece)
type IsUnion<T, U = T> = T extends any
? ([U] extends [T] ? false : true)
: never;
type Test1 = IsUnion<string>; // false
type Test2 = IsUnion<string | number>; // true
How the Magic Works:#
Let’s trace IsUnion<string | number>:
Udefaults to the full union:"string | number".T extends anytriggers distribution! It loops twice: once forstring, once fornumber.- Loop 1 (T is
string):[string | number] extends [string]➡️ False!- Returns
true.
- Loop 2 (T is
number):[string | number] extends [number]➡️ False!- Returns
true.
- The result recombines:
true | true➡️true.
If we pass a single type like string:
Uis"string".Tdoesn’t have multiple members, so it just evaluates once forstring.[string] extends [string]➡️ True!- Returns
false.
Summary & Next Steps#
In this episode:
- We learned that generic parameters are only distributed if they are “naked”.
- We disabled distribution by wrapping parameters in tuples:
[T] extends [U]. - We demonstrated how to generate unified generic arrays
(A | B)[]. - We built the legendary
IsUnion<T>utility by comparing distributed evaluation against non-distributive tuple checks.
If you understood IsUnion, congratulations! You have mastered one of the most mechanically complex quirks in the TypeScript compiler.
In Episode 44: The infer Keyword, we will unlock the ability to declare variable names dynamically inside the extends clause!

