Typing Callbacks in TypeScript: How to Define a Type for a Handler Function

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How to stop using any for callbacks and start living: a complete guide to typing handler functions in TypeScript

\n\n

Have you ever written something like callback: any in method parameters? If yes — welcome to the club. If not — you are either a genius or just starting your journey with TypeScript. Just kidding. But there is truth in every joke: according to Stack Overflow, the question "How to define type for a function callback (as any function type, not universal any)" gathers thousands of views. And that's no coincidence.

\n\n

The problem is that any is like a black hole: it sucks all the type safety out of your code. You lose autocompletion, argument checking, and the ability to catch bugs at compile time. But the most frustrating thing is that any as a callback type kills the very idea of TypeScript. Let's figure out how to properly type a callback function in a method parameter so that the code is both safe and elegant.

\n\n

Why is Function no better than any?

\n\n

A beginner's first instinct is to use the built-in Function type. After all, it's the "any function" type, so it should work. But no. Function is just as "garbage" a type as any. It provides no information about the signature: how many parameters the function takes, what it returns, what types the arguments are. You're just telling the compiler: "Trust me, I know what I'm doing." Spoiler: you usually don't.

\n\n
// Bad: no type checking\nfunction processData(callback: Function) {\n    callback(42, "hello"); // Error? Who knows\n}
\n\n

With this approach, you lose all the benefits of static typing. Let's fix that.

\n\n

Step 1. Basic callback typing: simple and clear

\n\n

The simplest and most correct way is to declare the function type directly in the parameter. For this, the arrow syntax (parameters) => return_type is used. This is the function type in TypeScript.

\n\n
// Good: explicitly define the signature\nfunction fetchData(url: string, onSuccess: (data: string) => void): void {\n    // ... some logic\n    const result = "Data from server";\n    onSuccess(result);\n}\n\n// Usage — with autocompletion and checking\nfetchData("/api/users", (data) => {\n    console.log(data.toUpperCase()); // data is string, all good\n});
\n\n

Notice the void in the return type. This means we don't expect any specific value from the callback. If the callback returns something, it will be ignored. If you expect a result, use a specific type.

\n\n

Step 2. Callback with multiple parameters

\n\n

Often callbacks accept more than one argument. For example, an error handler or an event with data. It's just as simple — list all parameters separated by commas.

\n\n
// Type for a callback with error and result\nfunction loadUser(\n    userId: number,\n    callback: (error: Error | null, user: { name: string; age: number } | null) => void\n): void {\n    // ... loading logic\n    const user = { name: "Anna", age: 30 };\n    callback(null, user);\n}\n\n// Usage\nloadUser(42, (err, user) => {\n    if (err) {\n        console.error("Error:", err.message);\n        return;\n    }\n    if (user) {\n        console.log(`Hello, ${user.name}!`);\n    }\n});
\n\n

See how TypeScript automatically suggests the types for err and user? This is callback function typing in action. No any, no magic — just strict checking.

\n\n

Step 3. Creating reusable types for callbacks

\n\n

If the same callback type is used in multiple places, don't duplicate it every time. Extract it into a separate type alias or interface. This improves readability and simplifies maintenance.

\n\n
// Decla                

Blogs

Book Recommendations