Allow All Except Specific Types

Jul 14, 2025 · 15min · TypeScript

> cd ..

Hey, I have a function and I want to make its parameter accept any types except number. How can I achieve that?

Sometimes you might need this, so let’s think about it.

First you may think about the built-in utility type Exclude, which allows you to exclude specific types from a union type. However, it doesn’t work for this case because it requires a union type to exclude from, but unfortunately any is NOT a union type.

bad-example.ts
declare function
function foo(arg: Exclude<any, number>): void
foo
(
arg: any
arg
:
type Exclude<T, U> = T extends U ? never : T

Exclude from T those types that are assignable to U

Exclude
<any, number>): void
function foo(arg: Exclude<any, number>): void
foo
('string') // ✅
function foo(arg: Exclude<any, number>): void
foo
(true) // ✅
function foo(arg: Exclude<any, number>): void
foo
(123) // ✅ this still works and raises no error 😢

As you can see, passing a number to the foo function does not raise any error, because Exclude<any, number> resolves to any. To address this, we need to narrow the type of the parameter to exclude number specifically.

We can introduce a generic type which will automatically inferred as the passed in type, and checks if it assignable to number. If it is, we can return never, which will effectively ban the type from being passed to the function.

The built-in utility type Exclude is an alias of what we are trying to do above, so we can write it in both ways:

declare function
function banNumber<T>(arg: T extends number ? never : T): void
banNumber
<
function (type parameter) T in banNumber<T>(arg: T extends number ? never : T): void
T
>(
arg: T extends number ? never : T
arg
:
function (type parameter) T in banNumber<T>(arg: T extends number ? never : T): void
T
extends number ? never :
function (type parameter) T in banNumber<T>(arg: T extends number ? never : T): void
T
): void
function banNumber<string>(arg: string): void
banNumber
('string') // ✅
function banNumber<boolean>(arg: boolean): void
banNumber
(true) // ✅
function banNumber<number>(arg: never): void
banNumber
(123) // ❌
Error ts(2345) ― Argument of type '123' is not assignable to parameter of type 'never'.
// or using `Exclude`
declare function
function banNumberWithExclude<T>(arg: Exclude<T, number>): void
banNumberWithExclude
<
function (type parameter) T in banNumberWithExclude<T>(arg: Exclude<T, number>): void
T
>(
arg: Exclude<T, number>
arg
:
type Exclude<T, U> = T extends U ? never : T

Exclude from T those types that are assignable to U

Exclude
<
function (type parameter) T in banNumberWithExclude<T>(arg: Exclude<T, number>): void
T
, number>): void
function banNumberWithExclude<string>(arg: string): void
banNumberWithExclude
('string') // ✅
function banNumberWithExclude<boolean>(arg: boolean): void
banNumberWithExclude
(true) // ✅
function banNumberWithExclude<number>(arg: never): void
banNumberWithExclude
(123) // ❌
Error ts(2345) ― Argument of type '123' is not assignable to parameter of type 'never'.

Because generic type is introduced, we can extend the usage it in a bunch of ways.

  • We can limit the type that can be passed in, to only allow certain types except number:
type
type UnionTypeWithNumber = string | number | null
UnionTypeWithNumber
= string | number | null
declare function
function banNumber<T extends UnionTypeWithNumber>(arg: Exclude<T, number>): void
banNumber
<
function (type parameter) T in banNumber<T extends UnionTypeWithNumber>(arg: Exclude<T, number>): void
T
extends
type UnionTypeWithNumber = string | number | null
UnionTypeWithNumber
>(
arg: Exclude<T, number>
arg
:
type Exclude<T, U> = T extends U ? never : T

Exclude from T those types that are assignable to U

Exclude
<
function (type parameter) T in banNumber<T extends UnionTypeWithNumber>(arg: Exclude<T, number>): void
T
, number>): void
function banNumber<"string">(arg: "string"): void
banNumber
('string') // ✅
function banNumber<null>(arg: null): void
banNumber
(null) // ✅
function banNumber<123>(arg: never): void
banNumber
(123) // ❌
Error ts(2345) ― Argument of type '123' is not assignable to parameter of type 'never'.
function banNumber<UnionTypeWithNumber>(arg: string | null): void
banNumber
(true) // ❌ because now only `string | null` is allowed
Error ts(2345) ― Argument of type 'boolean' is not assignable to parameter of type 'string'.
  • We can also use it to ban other types by introducing another generic type:
declare function
function banTypes<BannedTypes, T>(arg: Exclude<T, BannedTypes>): void
banTypes
<
function (type parameter) BannedTypes in banTypes<BannedTypes, T>(arg: Exclude<T, BannedTypes>): void
BannedTypes
,
function (type parameter) T in banTypes<BannedTypes, T>(arg: Exclude<T, BannedTypes>): void
T
>(
arg: Exclude<T, BannedTypes>
arg
:
type Exclude<T, U> = T extends U ? never : T

Exclude from T those types that are assignable to U

Exclude
<
function (type parameter) T in banTypes<BannedTypes, T>(arg: Exclude<T, BannedTypes>): void
T
,
function (type parameter) BannedTypes in banTypes<BannedTypes, T>(arg: Exclude<T, BannedTypes>): void
BannedTypes
>): void
function banTypes<string, number | boolean | null>(arg: number | boolean | null): void
banTypes
<string, number | boolean | null>(123) // ✅
function banTypes<string, number | boolean | null>(arg: number | boolean | null): void
banTypes
<string, number | boolean | null>('string') // ❌
Error ts(2345) ― Argument of type '"string"' is not assignable to parameter of type 'number | boolean | null'.
// ⚠️ Passing `any` to the second generic parameter won't work
function banTypes<string, any>(arg: any): void
banTypes
<string, any>('string')

Caution

In order to take advantage of this, you have to explicitly pass the second generic type to limit the type of the parameter, unless you give it a default type like T = SomeType.

You CANNOT use T = any because it will resolve to any as the parameter’s type, hence passing anything as the argument is acceptable.

> cd ..