Convert a union into a tuple even though unions have no order. The key move is extracting a single member via function overload resolution.
Unions give you no way to iterate their members, and UnionToTuple<T> needs exactly that.
UnionToTuple<T> turns 'a' | 'b' into a tuple like ['a', 'b']. Solving it teaches you how to peel a single member off a union, a technique built on UnionToIntersection and function overload resolution, with Exclude driving the recursion.
A union is an unordered structure, while a tuple is ordered. You must not assume the members of a union come in any particular order, or that any order survives when unions are created or transformed.
Hence in this challenge, any permutation of the elements in the output tuple is acceptable.
Your type should resolve to one of the following two types, but not a union of them.
UnionToTuple<1> // [1], and correct
UnionToTuple<'any' | 'a'> // ['any','a'], and corrector
[object Object]It shouldn't be a union of all acceptable tuples...
[object Object]A union can also collapse: some types absorb (or are absorbed by) others, and there is no way to prevent that absorption. See the following examples:
Equal<UnionToTuple<any | 'a'>, UnionToTuple<any>> // always true
Equal<UnionToTuple<unknown | 'a'>, UnionToTuple<unknown>> // always true
Equal<UnionToTuple<never | 'a'>, UnionToTuple<'a'>> // always true
Equal<UnionToTuple<'a' | 'a' | 'a'>, UnionToTuple<'a'>> // always trueImplement a type, UnionToTuple, that converts a union to a tuple.
As we know, union is an unordered structure, but tuple is an ordered, which implies that we are not supposed to preassume any order will be preserved between terms of one union, when unions are created or transformed.
Hence in this challenge, any permutation of the elements in the output tuple is acceptable.
Your type should resolve to one of the following two types, but NOT a union of them!
UnionToTuple<1> // [1], and correct
UnionToTuple<'any' | 'a'> // ['any','a'], and corrector
[object Object]It shouldn't be a union of all acceptable tuples...
[object Object]And a union could collapes, which means some types could absorb (or be absorbed by) others and there is no way to prevent this absorption. See the following examples:
Equal<UnionToTuple<any | 'a'>, UnionToTuple<any>> // will always be a true
Equal<UnionToTuple<unknown | 'a'>, UnionToTuple<unknown>> // will always be a true
Equal<UnionToTuple<never | 'a'>, UnionToTuple<'a'>> // will always be a true
Equal<UnionToTuple<'a' | 'a' | 'a'>, UnionToTuple<'a'>> // will always be a trueView on GitHub: https://tsch.js.org/730
Change the following code to make the test cases pass (no type check errors).
π Lifetime-License is leaving on August 10, 2026
Get it now for $29
One-time payment. Lifetime access to all pro challenges.
Three types make up the solution:
type UnionToIntersection<U> = (
U extends unknown ? (arg: U) => void : never
) extends (arg: infer I) => void
? I
: never
type LastOfUnion<U> =
UnionToIntersection<U extends unknown ? () => U : never> extends () => infer L
? L
: never
type UnionToTuple<T, Last = LastOfUnion<T>> = [T] extends [never]
? []
: [...UnionToTuple<Exclude<T, Last>>, Last]The strategy: repeatedly pluck one member out of the union, append it to a tuple, and recurse on the rest. The hard part is that first step, since a union gives you no handle on any individual member.
UnionToIntersectionThis is the classic trick from challenge #55 (worth doing first if you haven't). The distributive conditional U extends unknown ? (arg: U) => void : never wraps each union member in a function parameter, and inferring that parameter back out with infer I produces an intersection, because function parameters are contravariant: the inferred type must satisfy all members at once. Net effect: 'a' | 'b' becomes 'a' & 'b'.
LastOfUnion plucks one memberThis is where the real work happens. First we distribute U into a union of function types:
// U = 'a' | 'b'
// U extends unknown ? () => U : never
// evaluates to (() => 'a') | (() => 'b')Then UnionToIntersection turns that union into an intersection:
[object Object]Why wrap each member in () => U at all? Intersecting the raw members would destroy them: 'a' & 'b' is an unsatisfiable type from which neither original member can be recovered. Function types intersect gracefully instead; each member survives intact inside its own signature. An intersection of function signatures is what an overloaded function looks like. And when you match an overloaded function against () => infer L, TypeScript resolves infer using only the last overload signature. So L is inferred as 'b', and we've extracted a single, concrete member from the union.
Which member counts as "last" is an internal compiler detail, which is why the challenge accepts any permutation of the output. The trick guarantees you get one deterministic member, not any particular order.
ExcludeWith LastOfUnion in hand, UnionToTuple is a standard recursive build-up:
Last = LastOfUnion<T> is computed in a default type parameter, which binds a "local variable" so it isn't recomputed in both places it's used.[T] extends [never] checks for the empty union. The tuple wrapping matters: a bare T extends never would distribute over the union and, for T = never (an empty union), evaluate to never instead of a branch you can act on. Wrapping both sides in [...] disables distribution.Exclude<T, Last>, the union minus the member we just plucked, and spread the result before Last: [...UnionToTuple<Exclude<T, Last>>, Last].Tracing 'a' | 'b':
// UnionToTuple<'a' | 'b'> β [...UnionToTuple<'a'>, 'b']
// UnionToTuple<'a'> β [...UnionToTuple<never>, 'a']
// UnionToTuple<never> β []
// Result: ['a', 'b']UnionToTuple<never> β []: the base case fires immediately. The test verifies this indirectly: it extracts the element union back out of the result with an ExtractValuesOfTuple helper, and extracting values from the empty tuple gives never.UnionToTuple<any>: any isn't a union, so LastOfUnion<any> just returns any and the result is [any]. The tests also run UnionToTuple<any | 1> through ExtractValuesOfTuple and assert the result against both spellings any | 1 and any, two ways of writing the same type, because any absorbs 1 before your type ever sees it. The same absorption applies to unknown | 'a', never | 'a', and duplicate members like 'a' | 'a'; your type can't (and needn't) resist it.undefined | void | 1: undefined is absorbed into void in this union, so the expected output only contains void | 1.The member-plucking trick is the standard way to iterate over a union, and it reappears in many other hard challenges.
This challenge is originally from here.