How to Define a TypeScript Function Type Callback That Accepts Any Signature Without Using `any`

Use a generic function type with unknown constraints: type Callback<Args extends readonly unknown[] = unknown[], R = unknown> = (...args: Args) => R;

The microsoft/TypeScript repository demonstrates that when you need a flexible typescript function type callback, you should avoid the universal (...args: any[]) => any pattern. Instead, generic type parameters preserve type safety while allowing any function signature, letting the compiler infer argument and return types at the call site.

Why Universal Function Types Break Type Safety

A naïve approach to accepting "any function" uses the type (...args: any[]) => any. This creates a universal type that disables type checking for both parameters and return values. Once you annotate a callback with this type, the compiler can no longer verify that arguments passed to the function match its expected signature, nor can it infer what the function returns. This silently erases type information and allows invalid operations at runtime.

The Generic Callback Pattern

The robust solution defines a generic callback type using unknown instead of any. This pattern captures the argument list and return type as type parameters, maintaining type awareness without restricting the specific shape of the function.

Basic Generic Implementation

Define the type with two parameters: Args for the tuple of argument types and R for the return type:

type Callback<Args extends readonly unknown[] = unknown[], R = unknown> = (
    ...args: Args
) => R;

This definition uses unknown with constraints to prevent the type from becoming a catch-all any. The defaults (unknown[] and unknown) allow the type to represent any function when specific parameters are not provided, while concrete instantiations preserve full type information.

Usage in Higher-Order Functions

You can use this pattern in higher-order functions that invoke callbacks safely:

function invoke<T extends unknown[], R>(cb: Callback<T, R>, ...args: T): R {
    return cb(...args);
}

// Example: explicit typing
const ok: Callback<[string, number], boolean> = (s, n) => s.length > n;
const result = invoke(ok, "hello", 2); // result is inferred as boolean

The compiler validates that "hello" and 2 match the [string, number] tuple and that result receives the boolean return type.

How TypeScript Implements This Internally

The TypeScript compiler itself distinguishes between internal utility types and public-facing generics. According to the source code in src/compiler/core.ts at line 1934, the repository defines an internal AnyFunction type for compiler use:

// Located at src/compiler/core.ts#L1934
type AnyFunction = (...args: never[]) => void;

This internal type serves as a placeholder for "any callable" within the compiler's implementation details. However, for public APIs and user code, the TypeScript codebase favors explicit generic patterns like the Callback type shown above, which preserve type relationships rather than discarding them.

Complete Code Examples

Inferring Types Automatically

When you wrap a function using generics, TypeScript can infer the specific signature from the supplied callback:

function wrap<T extends unknown[], R>(cb: Callback<T, R>) {
    return (...args: T) => cb(...args);
}

const add = (a: number, b: number) => a + b;
const safeAdd = wrap(add); // safeAdd: (a: number, b: number) => number

Mapping Values with Callbacks

Generic callbacks work seamlessly with utility functions that transform data:

function mapCallback<A, B>(cb: Callback<[A], B>, value: A): B {
    return cb(value);
}

const toString: Callback<[number], string> = n => n.toString();
const str = mapCallback(toString, 42); // "42"

Comparison with Non-Generic Alternatives

Type Safety Level Use Case
(...args: any[]) => any None (universal) Avoid in public APIs
(...args: unknown[]) => unknown Low When you only need "callable" verification
Callback<T, R> High When parameter and return types must be preserved

Summary

  • Avoid (...args: any[]) => any for callback types because it creates a universal type that disables type checking.
  • Use a generic Callback<Args, R> definition with unknown constraints to accept any function signature while preserving type information.
  • Reference src/compiler/core.ts line 1934 to see how TypeScript internally uses restricted function types for compiler utilities.
  • Leverage type inference with higher-order functions like invoke, mapCallback, or wrap to maintain strict type safety without verbose annotations.

Frequently Asked Questions

What is the difference between (...args: any[]) => any and a generic callback type?

(...args: any[]) => any is a universal type that tells TypeScript to ignore type checking for both arguments and return values. A generic callback type like Callback<T, R> captures the specific argument tuple T and return type R, allowing the compiler to verify that calls match the function signature and to infer the result type correctly.

Why should I avoid using any for function callbacks in TypeScript?

Using any for callbacks disables the compiler's ability to catch mismatched arguments or incorrect return value usage. It also prevents IntelliSense and autocomplete features from working correctly in IDEs. The unknown type with generics provides the same flexibility without sacrificing type safety.

How does TypeScript's own compiler define internal function types?

According to the microsoft/TypeScript source code, the internal AnyFunction type is defined in src/compiler/core.ts at line 1934 as (...args: never[]) => void. This internal type is restricted to compiler-specific use cases, while the codebase encourages public APIs to use more expressive generic patterns that retain type information.

Can I constrain the return type to void for callback functions?

Yes, you can create a specialized version by setting R = void or constraining the generic: type VoidCallback<Args extends unknown[]> = Callback<Args, void>. This ensures the callback does not return a usable value, though TypeScript will still allow functions that return values to be assigned to it (it only checks that you don't use the return value).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client