Any type except X

TypeScript makes it very easy to say "this item can be any one of these types" using unions. But it's hard to go the other way. It's hard to say "this item can be any of these types except this type".

For example, suppose I want to say this function accepts an item of any type except null.

function print(x: string | number | boolean | etc) {
  console.log(x);
}

You can see this is getting pretty long pretty quickly. Fortunately, there's a built-in type called NonNullable that you can use for exactly this.

function print<T>(x: NonNullable<T>): void {
  console.log(x);
}

// Argument of type 'null' is not assignable to parameter of type 'never'.ts(2345)
print(null);

NonNullable is only for null (and undefined), though. How about a more generic solution? Enter, Exclude.

function print<T>(x: Exclude<T, null>): void {
  console.log(x);
}

// Argument of type 'null' is not assignable to parameter of type 'never'.ts(2345)
print(null);

but Exclude can be used with other types too!

// can print anything except strings
function print<T>(x: Exclude<T, string>): void {
  console.log(x);
}

or

// can print anything except strings and numbers
function print<T>(x: Exclude<T, string | number>): void {
  console.log(x);
}

print("no"); // error
print(1); // error
print(true); // ok
print(null); // ok

That's all you really need to know, but I'm going to dive into how these work because it's pretty cool. Here's how NonNullable is defined:

type NonNullable<T> = T extends null | undefined ? never : T;

It's a type that takes a type parameter generates a type! Sometimes I find it easier to understand these if I rewrite them as functions instead, so here is a similar function.

function nonNullable(t) {
  if (t === null || t === undefined) {
    return never;
  }
  return t;
}

It's pseudocode, but you get the idea. There are two big takeaways here.

One is that the never type will always fail. This is actually very useful. Think of all the times you would want the TypeScript compiler to tell you something is wrong:

"TypeScript, show an error if someone passes null into this function."

"TypeScript, show an error if someone passes in an object that doesn't include this property."

"TypeScript, this function is deprecated, so don't allow anyone to use it."

Any time you want the compiler to tell you something is wrong, and there isn't a standard TypeScript way to do that, never could be an option.

But how do you use never for this? This is the second big takeaway. You can basically write a function in TypeScript that is all types. It takes types as arguments and it returns a type as the return value. NonNullable is such a "function". It returns either the type you gave it, or never.

Using these two ideas, you can write a type "function" yourself, which takes in one or more types as arguments. If you don't want the user to be able to use those types, just return never!

Further Reading