To pass a callback function safely in TypeScript, you define its type (the parameter list and return type) and annotate the callback parameter with that type. This lets the compiler catch mismatched arguments, missing parameters, or wrong return values before the code runs.
Why Callback Types Matter
Callbacks are functions you pass to another function to be invoked later—after an event, a network response, or a timer. Without explicit types, TypeScript treats the callback as any, which disables type checking entirely. A single typo in a parameter name or a missing argument becomes a runtime error that is hard to trace. Typed callbacks turn those errors into red squiggles in your editor.
Consider a simple timer function:
function runTimer(callback: () => void) {
setTimeout(callback, 1000);
}
runTimer(() => console.log('done')); // OK
runTimer((x: number) => console.log(x)); // Error: expected 0 arguments
The type () => void says the callback takes no parameters and returns nothing. Passing a function that expects an argument is immediately flagged.
Basic Syntax for Callback Types
The syntax for a function type is (param1: Type1, param2: Type2) => ReturnType. You can use it inline or as a type alias:
type Callback = (error: Error | null, result: string) => void;
function fetchData(cb: Callback) {
// ...
}
Here the callback must accept two parameters: an Error or null, and a string. It returns void, meaning the caller ignores any return value. You can also use unknown or any for the return type if you truly don’t care, but void is safer because it allows the callback to return something without causing an error (TypeScript allows a function returning a value to be assigned to a void callback type).
Using Type Aliases and Interfaces
For reusable callbacks, define a type alias or interface. This is especially useful when the same callback shape appears in multiple functions.
interface EventHandler {
(event: MouseEvent): void;
}
function addClickListener(handler: EventHandler) {
// ...
}
Interfaces with a call signature describe a function type. Type aliases are more common for simple callbacks:
type ProgressCallback = (percent: number) => void;
Using a named type makes the code self-documenting and easier to change later.
Optional Parameters and Defaults
Callbacks often have optional parameters. You can mark them with ? or provide a default value in the implementation.
type Callback = (value: number, index?: number) => void;
function forEachItem(items: number[], cb: Callback) {
items.forEach((item, i) => cb(item, i));
}
When the callback is invoked, the optional parameter may be undefined. The callback implementation must handle that possibility. Alternatively, you can use a default value in the callback definition:
function myCallback(value: number, index: number = 0) {
// ...
}
This is safe because TypeScript knows the default is applied when the argument is omitted.
Return Type Safety: void vs. any
One common mistake is using any as the return type. This disables checking on what the callback returns. Prefer void when the callback’s return value is not used. If you need the return value, specify it explicitly:
type Comparator = (a: number, b: number) => number;
function sortNumbers(arr: number[], cmp: Comparator) {
return arr.sort(cmp);
}
Here the callback must return a number (negative, zero, or positive) to indicate ordering. TypeScript will enforce that the callback returns a number, not undefined.
Another subtlety: a callback typed to return void can still be a function that returns a value. TypeScript allows this because the caller ignores the result. But if you type it as any, you lose the ability to catch a callback that accidentally returns the wrong type when you later decide to use the return value.
Common Pitfalls and Solutions
- Mismatched parameter count: A callback with fewer parameters than expected is fine (extra arguments are ignored in JavaScript), but one with more parameters is an error. TypeScript checks this.
- Using
Functiontype: TheFunctiontype accepts any function, but it loses all parameter and return type information. Avoid it; use a concrete signature. - Optional parameters in callbacks: If the callback may be called with fewer arguments, mark them optional. If the callback must handle a variable number, use rest parameters:
(...args: number[]) => void. - Context (this): Arrow functions capture the surrounding
this, which is usually what you want. If you need a specificthis, use a function type withthisparameter:(this: Window, e: Event) => void.
Another common issue is passing a method that uses this. For example, obj.method loses its this binding when passed as a callback. Use an arrow function or bind it: obj.method.bind(obj).
Type Inference and Contextual Typing
TypeScript often infers the callback parameter types from the expected type. This is called contextual typing. For example, with Array.map, the callback’s parameter is automatically typed as the array element type:
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2); // n is number
You don’t need to annotate n. But when you define your own function that accepts a callback, TypeScript cannot infer the callback’s parameters unless you specify them. So you must provide the type in the function signature.
If you want to allow any callback that takes no arguments and returns anything, you can use () => unknown. But be careful: unknown forces the caller to narrow the type before using the result.
Higher-Order Functions and Generics
When a function returns a callback or takes a callback that itself takes a callback, types can get complex. Generics help keep them flexible and safe.
function withLogging<T>(fn: (value: T) => void): (value: T) => void {
return (value) => {
console.log('Calling with', value);
fn(value);
};
}
Here the generic T ensures the returned callback accepts the same type as the input callback. This prevents accidentally mixing types.
Another pattern is a callback that returns a Promise. Type it as () => Promise<void> to make async behavior explicit. This helps avoid unhandled promise rejections.
Frequently Asked Questions
Can I pass a callback with fewer parameters than the type specifies?
Yes. TypeScript allows a function with fewer parameters to be assigned to a callback type with more parameters. This is safe because the extra arguments will be ignored when the callback is invoked. For example, a callback typed as (a: number, b: number) => void can be satisfied by (a: number) => {}.
What is the difference between void and undefined as a return type?
void means the return value is not used and can be anything. undefined means the function must explicitly return undefined (or nothing). In practice, for callbacks, use void unless you specifically need to check for undefined.
How do I type a callback that can be called with different argument types?
Use a union type or a generic. For example, (value: string | number) => void accepts either. If the callback must handle any type, use (value: unknown) => void and narrow inside the callback.
Why does TypeScript complain when I pass an async function as a callback?
An async function returns a Promise, not void. If the callback type expects () => void, a function returning Promise<void> is not assignable because the return type is incompatible. You can either change the callback type to () => Promise<void> or wrap the async call in a non-async function that ignores the promise.
Should I use Function type or a specific signature?
Always use a specific signature. The Function type accepts any function, but you lose all type safety. It’s equivalent to using any for the callback, which defeats the purpose of TypeScript.
How can I make a callback parameter optional?
Use the ? modifier on the parameter: callback?: () => void. Inside the function, check if it exists before calling: if (callback) callback(). This is common for optional event handlers.
Related
Formula used
in t | Callback delay (ms) | ms | 1000 | 0 | 60000 in d | Number of calls | - | 3 | 1 | 100 out total | Total time (ms) | ms | t * d | 0 out min | Min time (ms) | ms | t | 0 out max | Max time (ms) | ms | t * d | 0
Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Campus Tech Innovation Hubs: What They Are
- Poco C51 Airtel Prepaid Locked Variant India?
- Jenkins vs GitHub Actions: Which CI/CD Fits Your Team?
- Turn Off Backup Encryption in iTunes?
- Shared VPS Hosting: Is It Better?
- Disk Defrag Pro: What It Does and Whether You Need It