Typescript is a typed superset of Javascript. For this challenge, we'll consider the following classic basic types:

  • string
  • number
  • boolean
  • undefined
  • null

And two "meta" types:

  • any
  • never

The type any annotates that any type is valid:

const a: any = "foo";
const b: any = 42;
const c: any = true;
const d: any = undefined;

Whereas never annotates that no type is valid:

const e: never = <no valid literal assignment possible>;

We'll also consider two compositions of types: unions and intersections, annotated as | and & respectively.

A union type expects either one or the other type:

const f: string | number = "foo";
const g: string | number = 42;

While an intersection creates a combination of both types:

const h: string & any = "foo";
const i: string & any = 42;
const j: string & number = <no valid literal assignment possible>;

The order of operands within unions and intersections doesn't matter, string | number is equal to number | string.

The challenge

Given one of the above type declarations, return the resulting resolved type. The input will be one of the following:

  • a basic type as listed above or;
  • a meta type as listed above or;
  • a union of two basic and/or meta types or;
  • an intersection of two basic and/or meta types.


// All basic and meta types simply map to themselves
string → string
number → number
any → any
never → never

// Unions between basic types create unions except with themselves
string | number → string | number
string | undefined → string | undefined
string | string → string

// Intersections between basic types create never except with themselves
string & number → never
string & undefined → never
string & string → string

// Unions with any create any
any | string → any
any | never → any

// Intersections with any result in any except with never
any & string → any
any & number → any
any & never → never

// Unions with never yield the other half
never | string → string
never | any → any
never | never → never

// Intersections with never return never
never & string → never
never & any → never
never & never → never

A good way to get a feel for the types is to try out the Typescript playground.

Any reasonable and consistent input-output format, standard loop holes, .

  • 2
    \$\begingroup\$ Why does any & something not give something? I would imagine a number is not both any and string \$\endgroup\$
    – hyper-neutrino
    Commented Apr 14, 2021 at 10:38
  • 1
    \$\begingroup\$ @hyper-neutrino It's easier explained with object types, but I omitted them to avoid making the challenge needlessly complex. A simple example is const x: { foo: string } & { bar: number } = { foo: "foo", bar: 42 };. \$\endgroup\$
    – Etheryte
    Commented Apr 14, 2021 at 10:44
  • 2
    \$\begingroup\$ oh, okay. that makes a bit more sense but still quite strange and definitely inconvenient for golfing \$\endgroup\$
    – hyper-neutrino
    Commented Apr 14, 2021 at 10:45
  • 1
    \$\begingroup\$ @user No, all the types you have to deal with are outlined in the challenge section. \$\endgroup\$
    – Etheryte
    Commented Apr 14, 2021 at 13:53
  • 3
    \$\begingroup\$ FYI, this challenge encodes a fundamental misunderstanding of TypeScript. any is not the type that annotates that any type is valid; unknown is. Rather, any is a non-type that’s used for interoperability with untyped JavaScript, behaving contradictorily as both a subtype and supertype of every type. This is why any & string is any, while unknown & string is string as you’d expect. any should not be used in typed code, while unknown is safe. \$\endgroup\$ Commented Apr 16, 2021 at 17:01

7 Answers 7


JavaScript (ES6),  113 110 106  99 bytes

f=b=>(N='never',[x,o,y]=b.split` `,X=x<f?2:x!=N,Y=y<f?2:y!=N,!y|x==y?x:X*Y>1?'any':o>f?X?Y?b:x:y:N)

Try it online!


Although this function is not recursive, it is named explicitly and uses the argument b so that we can test if a string is any (which is the first of all types in lexicographical order) by comparing it with \$f\$. The same trick is used to distinguish between & and |.

f = b => ...

We load the first type in \$x\$, the operator in \$o\$ and the second type in \$y\$. Both \$o\$ and \$y\$ may be undefined if only one type is passed in the input string.

[x, o, y] = s.split` `

The pair of strings \$(x,y)\$ is turned into a pair of integers \$(X,Y)\$ by applying the following rules:

  • if the type is any, map it to \$2\$
  • if the type is never, map it to \$0\$
  • if the type is anything else, map it to \$1\$
N = 'never'
X = x < f ? 2 : x != N
Y = y < f ? 2 : y != N

We then apply the following logic:

!y | x == y ? // if y is undefined or x = y:
  x           //   return x
:             // else:
  X * Y > 1 ? //   if X * Y > 1, which means that one of the types
              //   is 'any' and the other one is *not* 'never':
    'any'     //     return 'any'
  :           //   else:
    o > f ?   //     if this is a union, i.e. o = '|':
      X ?     //       if X is not equal to 0:
        Y ?   //         if Y is not equal to 0:
          b   //           return the original union
        :     //         else (Y = 0 / y = 'never'):
          x   //           return x
      :       //       else (X = 0 / x = 'never'):
        y     //         return y
    :         //     else (intersection):
      N       //       return 'never'

C++ TMP, 476 466 bytes

I'm surprised I got this far with it. U is union, I is intersection, R is the solution function that defines the solution type t. Online version with tests.

#define Z template
#define C class
#define K Z<C A>C R
#define T(x){public:using t=x;};
C string;C number;C boolean;C undefined;C null;C any;C never;using X=any;using Y=never;Z<C...>C U;Z<C...>C I;K T(A)K<U<A,A>>T(A)K<I<A,A>>T(A)Z<C A,C B>C R<I<A,B>>T(Y)K<U<A,X>>T(X)K<U<X,A>>T(X)K<I<X,A>>T(X)K<I<A,X>>T(X)Z<>C R<I<Y,X>>T(Y)Z<>C R<I<X,Y>>T(Y)K<U<A,Y>>T(A)K<U<Y,A>>T(A)Z<>C R<U<Y,X>>T(X)Z<>C R<U<X,Y>>T(X)Z<>C R<U<Y,Y>>T(Y)K<I<Y,A>>T(Y)K<I<A,Y>>T(Y)Z<>C R<I<Y,Y>>T(Y)

I'm certain this could be compacted further, maybe even to around 350 bytes. If anyone wants to give it a try, here is the version without preprocessor directives:

struct string;
struct number;
struct boolean;
struct undefined;
struct null;
struct any;
struct never;
using X=any;
using Y=never;
template<class A>struct T{using t=A;};
template<class...>struct U;
template<class...>struct I;
template<class A>struct R:T<A>{};
template<class A>struct R<U<A,A>>:T<A>{};
template<class A>struct R<I<A,A>>:T<A>{};
template<class A,class B>struct R<I<A,B>>:T<Y>{};
template<class A>struct R<U<A,X>>:T<X>{};
template<class A>struct R<U<X,A>>:T<X>{};
template<class A>struct R<I<X,A>>:T<X>{};
template<class A>struct R<I<A,X>>:T<X>{};
template<>struct R<I<Y,X>>:T<Y>{};
template<>struct R<I<X,Y>>:T<Y>{};
template<class A>struct R<U<A,Y>>:T<A>{};
template<class A>struct R<U<Y,A>>:T<A>{};
template<>struct R<U<Y,X>>:T<X>{};
template<>struct R<U<X,Y>>:T<X>{};
template<>struct R<U<Y,Y>>:T<Y>{};
template<class A>struct R<I<Y,A>>:T<Y>{};
template<class A>struct R<I<A,Y>>:T<Y>{};
template<>struct R<I<Y,Y>>:T<Y>{};
  • \$\begingroup\$ Holy moly, C++ templates was not a solution I expected to see for this, but I'm very glad I did. Nice work, very impressive. \$\endgroup\$
    – Etheryte
    Commented Apr 16, 2021 at 15:55
  • \$\begingroup\$ This is nightmare fuel. \$\endgroup\$ Commented Aug 4, 2022 at 12:24

Python 3, 261 bytes

lambda x,o="&",y="":("any"in x+y)*(o<"|")*-~-("never"in x+y)*"any"or(lambda r:r==31and"any"or"|".join("ubsnnnotuudormleliblfene iagr nn   e    d"[i::5]for i in range(5)if r&2**i)or"never")(eval(o.join(str(min(31,2**"dormlyv".find(k[2])%64))for k in[x,y or x])))

Try it online!

-1 byet thanks to ovs

  • \$\begingroup\$ (1-("never"in x+y)) can be shortened to -~-("never"in x+y) or 0**("never"in x+y). \$\endgroup\$
    – ovs
    Commented Apr 14, 2021 at 12:24
  • \$\begingroup\$ @ovs 0** is very clever. also i did not realize using complement would actually make it short enough; should've tried more combinations \$\endgroup\$
    – hyper-neutrino
    Commented Apr 14, 2021 at 13:22

Perl 5 -apl, 73 bytes


Try it online!

Uses , instead of | so that the sort works without a customized function.


Python 3, 177 bytes

import enum;
def f(x):
	class T(enum.Flag):never=0;string=2;number=1;boolean=4;undefined=8;null=16;any=31
	return[str(eval(x,{},T))[2:],"any"][("&"in x)*("y"in x)*("v"not in x)]

Try it online!


Retina 0.8.2, 120 bytes

never \| (\w+)|(\w+) (\| never|[|&] \1)
any & never|never & any
any [|&] \w+|\w+ [|&] any
\w+ & \w+

Try it online! Link includes test cases. Explanation:

never \| (\w+)|(\w+) (\| never|[|&] \1)

Simplify unions with never and self-unions and intersections.

any & never|never & any

Simplify intersections between any and never.

any [|&] \w+|\w+ [|&] any

Simplify any other intersections of any.

\w+ & \w+

Simplify any other intersections.


TypeScript's type system, 38 bytes

type F<A,B=A,C=A>=B extends"&"?A&C:A|C

Try it at the TS playground

Takes input like F<string> or F<never,"|",number>.

This is admittedly a very cheaty I/O method, here's a more normal one:

TypeScript's type system, 163 bytes

type F<A,B=A,C=A,D={string:string;number:number;boolean:boolean;undefined:undefined;null:null;any:any;never:never},X=D[A],Y=D[C]>=B extends"&"?X&Y:X|Y

Try it at the TS playground


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.