Turn A | B | C into A & B & C. There is no built-in operator for this; distributive conditionals and contravariant inference get you there.
There is no operator that turns A | B | C into A & B & C. A variance trick gets you one anyway.
UnionToIntersection<U> converts a union of types into an intersection of those same types. The solution relies on two deep mechanics working together: distributive conditional types and contravariant inference of function parameters. This exact pattern shows up all over real-world type libraries, from merging overloads to combining plugin configurations.
For example
[object Object]Implement the advanced util type UnionToIntersection<U>
For example
[object Object]View on GitHub: https://tsch.js.org/55
Change the following code to make the test cases pass (no type check errors).
The solution in full:
type UnionToIntersection<U> = (
U extends unknown ? (arg: U) => void : never
) extends (arg: infer I) => void
? I
: neverIt's short, but almost every character is doing something subtle.
The inner expression U extends unknown ? (arg: U) => void : never looks like a no-op: every type extends unknown, so the condition is always true. It's there for a side effect.
When the type being checked before extends is a bare generic parameter, the conditional type becomes distributive: TypeScript applies it to each union member separately and unions the results. For U = 'foo' | 42 | true, the expression evaluates member by member:
// 'foo' → (arg: 'foo') => void
// 42 → (arg: 42) => void
// true → (arg: true) => voidSo the whole inner expression becomes:
[object Object]We haven't gotten rid of the union yet. We've just moved every member into function parameter position, and that relocation is the entire point.
Now the outer check asks: does that union of functions extend (arg: infer I) => void? To answer, TypeScript must find a single type I that works as the parameter for all of the function types at once.
The key rule: function parameters are contravariant. A function that can safely stand in for all three of those functions must accept 'foo' and 42 and true, since any caller might pass any of them. So the inferred parameter has to satisfy every constraint simultaneously, and when TypeScript infers a type variable from multiple contravariant positions, it intersects the candidates instead of unioning them:
[object Object]That variance flip, union in, intersection out, is what makes the solution work. The same infer in a covariant position (like a return type) would have produced a union.
If you tried U extends (arg: infer I) => void ? I : never directly, distribution would kick in on the outer conditional and each member would be checked on its own. For this page's running example that's an immediate dead end: none of 'foo', 42 or true is a function type, so every distributed branch takes the never path and the whole result is never. Even when the members are function types, like the test case (() => 'foo') | ((i: 42) => true), you'd infer one I per member and union the results right back together: 42 from the second member, unknown from the parameter-less first, giving unknown rather than any intersection. Doing the distribution first and the inference second, on the already-assembled union of functions (wrapped in parentheses so it's no longer a bare type parameter), lets the inference see all members in a single non-distributive check.
UnionToIntersection<'foo' | 42 | true> yields 'foo' & 42 & true. That intersection is unsatisfiable by any value (nothing is both a string and a number), but as a type it's what the challenge asks for, and Equal confirms the structure matches. TypeScript doesn't collapse it to never because intersections you form directly are preserved as written; the eager reduction of literal intersections to never only happens when they appear as members of a union being normalized.UnionToIntersection<(() => 'foo') | ((i: 42) => true)> yields (() => 'foo') & ((i: 42) => true), an intersection of function types, which behaves like an overloaded function. This test shows the trick works on function members too: they get wrapped one level deeper during step 1 and come back out intact.Once you understand this pattern, you'll recognize it as a building block: several other hard challenges (like UnionToTuple) start by calling UnionToIntersection under the hood.
This challenge is originally from here.