#730β€’Hard

Union to Tuple

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 correct

or

[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 true

Challenge Instructions: Union to Tuple

Hard

Implement 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 correct

or

[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 true

View 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.

Loading...

Detailed Explanation

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.

Helper 1: UnionToIntersection

This 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'.

Helper 2: LastOfUnion plucks one member

This 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.

The main type: recursion with Exclude

With LastOfUnion in hand, UnionToTuple is a standard recursive build-up:

Tracing 'a' | 'b':

// UnionToTuple<'a' | 'b'>  β†’  [...UnionToTuple<'a'>, 'b']
// UnionToTuple<'a'>        β†’  [...UnionToTuple<never>, 'a']
// UnionToTuple<never>      β†’  []
// Result: ['a', 'b']

Edge cases the tests cover

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.

Share this challenge

Learn the Concepts