Skip to content
webtype.orgwebtype.org#238 union-to-intersection · par 5

    ↑↓ move · ⏎ open · esc close

    No. 238 · August 21, 2026 · Brutal

    UnionToIntersection

    Implement `UnionToIntersection<U>` so `A | B` becomes `A & B`. There is no operator for this — the answer exploits how the compiler infers from function parameters.

    01

    Try the puzzle yourself

    Par 5

    Puzzle

    union-to-intersection.ts
    Stroke 1 of 5Not run yet

    Replace ??? — your solution is checked against the cases below. Tab indents; press Escape then Tab to move focus out.

    Checks

    4
    • UnionToIntersection<{ a: string } | { b: number }>
      { a: string } & { b: number }
    • UnionToIntersection<string>
      string
    • UnionToIntersection<'a' | 'b'>
      never
    • UnionToIntersection<{ x: 1 }>
      { x: 1 }

    How a check is judged Exact type equality, not assignability — an intersection is not the same as the flattened object.

    How everyone did

    Fewer than 5 people have solved this one so far. The distribution appears once there is enough of a sample to mean anything.

    Short game

    Fewest characters

    No public scores yet.

    Archive
    02

    Annotated solution

    Published August 22, 2026

    The solution

    type UnionToIntersection<U> = (
      U extends unknown ? (arg: U) => void : never
    ) extends (arg: infer I) => void
      ? I
      : never

    The common wrong answer

    type UnionToIntersection<U> = U extends unknown ? U : never

    This distributes and reassembles, which is an identity function: unioning the members back together returns the union you started with. Distribution alone can never build an intersection — something has to change the variance.

    Line by line

    1. U extends unknown ? (arg: U) => void : never

      A distributive conditional whose only job is to wrap each member in a function. `A | B` becomes `((arg: A) => void) | ((arg: B) => void)` — still a union, but now the members differ only in a parameter position.

    2. extends (arg: infer I) => void

      Asking a union of functions for one parameter type forces the compiler to find a type every member would accept. Because parameters are contravariant, that type is the *intersection* of the candidates — which is exactly the answer.

    3. UnionToIntersection<'a' | 'b'>

      The honest consequence: `"a" & "b"` describes a value that is both string literals at once, which nothing can be, so it collapses to `never`. The type is behaving correctly even though the answer looks like a failure.

    Takeaway

    Variance is a tool, not just a rule to obey. Inference from a covariant position gives you a union; from a contravariant one it gives you an intersection — and that asymmetry is the only way to cross between them.

    Uses