Question
In TypeScript, I can declare a function parameter using the Function type, but that does not seem type-safe. For example:
class Foo {
save(callback: Function): void {
// Do the save
const result: number = 42;
// Can TypeScript ensure at compile time that the callback
// accepts exactly one parameter of type number?
callback(result);
}
}
const foo = new Foo();
const callback = (result: string): void => {
alert(result);
};
foo.save(callback);
Here, save passes a number to the callback, but the callback expects a string, and using Function does not properly protect against that mismatch.
Is there a type-safe way to define a function parameter in TypeScript so the callback signature is checked at compile time?
In short: is there a TypeScript equivalent to a strongly typed delegate in .NET?
Short Answer
By the end of this page, you will understand why using Function is too broad in TypeScript, how to replace it with a typed function signature, and how TypeScript checks callback parameter types at compile time. You will also see common callback patterns, real-world usage, and how this compares to delegates in languages like C#.
Concept
In TypeScript, the type Function means some callable value, but it does not describe the function's parameter types or return type in a useful way.
That is why this is unsafe:
save(callback: Function): void
It tells TypeScript only that callback is a function. It does not tell TypeScript:
- how many arguments the function expects
- what types those arguments should be
- what the function returns
If you want type safety, you should use a function type signature.
For your example, the callback should accept one number and return void:
save(callback: (result: number) => void): void
Now TypeScript knows the exact shape of the callback. If you try to pass a function that expects a string instead of a number, TypeScript can report an error.
This matters because callbacks are everywhere in real programs:
- event handlers
- array methods like
mapandfilter - async APIs
- database and file operations
- custom hooks and utility functions
If callback types are vague, mistakes slip through. If callback types are explicit, your editor can autocomplete correctly, your code becomes easier to understand, and many bugs are caught before runtime.
This is very similar to a strongly typed delegate in .NET. In TypeScript, the equivalent idea is usually expressed with:
- an inline function type, like
(value: number) => void - a type alias, like
type SaveCallback = (value: number) => void - sometimes an interface with a call signature
So yes: TypeScript absolutely supports strongly typed function parameters.
Mental Model
Think of Function as writing "some tool" on a box label.
That tells you the box contains a tool, but not whether it is:
- a hammer
- a screwdriver
- a wrench
A typed function signature is like labeling the box with exact instructions:
- takes one number
- returns nothing
Now anyone using the box knows exactly what fits.
So:
Function= "this is a function somehow"(result: number) => void= "this function must take one number and return nothing"
The second label is what gives you safety.
Syntax and Examples
Core syntax
Use a function type instead of Function:
(result: number) => void
This means:
- the function takes one parameter named
result - that parameter must be a
number - the function returns
void
Your example, typed safely
class Foo {
save(callback: (result: number) => void): void {
const result = 42;
callback(result);
}
}
const foo = new Foo();
const goodCallback = (result: number): void => {
console.log(result);
};
foo.save(goodCallback);
This works because the callback matches the expected signature.
Example that fails type checking
class Foo {
save(callback: (result: number) => void): void {
const result = 42;
callback(result);
}
}
const badCallback = (result: string): void => {
console.log(result);
};
foo.save(badCallback);
TypeScript should report a type mismatch because string is not compatible with number.
Using a type alias
This is often cleaner in real code:
type SaveCallback = (result: number) => void;
class Foo {
save(callback: SaveCallback): void {
const result = 42;
callback(result);
}
}
A type alias makes the callback reusable and easier to read.
Callback with a return value
Function types can also describe return values:
type Formatter = (value: number) => string;
const formatNumber: Formatter = (value) => `Value: ${value}`;
Here, the callback must return a string.
Step by Step Execution
Consider this code:
class Foo {
save(callback: (result: number) => void): void {
const result = 42;
callback(result);
}
}
const foo = new Foo();
const callback = (result: number): void => {
console.log("Saved value:", result);
};
foo.save(callback);
Step by step:
Foodefines a method namedsave.saveexpects one argument: a function.- That function must match this shape:
- one parameter
- parameter type is
number - return type is
void
const result = 42;creates a number.callback(result);calls the provided function with42.- Since
callbackaccepts anumber, the call is valid. - The console prints:
Saved value: 42
Now compare with a bad callback:
const callback = (result: string): void => {
console.log(result);
};
When TypeScript checks foo.save(callback), it sees:
saverequires(result: number) => voidcallbackis(result: string) => voidstringdoes not matchnumber
So the type checker can reject it before the program runs.
Real World Use Cases
1. API request callbacks
A function may fetch data, then pass the result to a callback:
type UserCallback = (userId: number) => void;
function loadUser(callback: UserCallback): void {
callback(101);
}
This prevents accidentally writing a callback that expects the wrong type.
2. Event processing
You may define custom event handlers:
type MessageHandler = (message: string) => void;
function onMessage(handler: MessageHandler): void {
handler("Hello");
}
3. Data transformation
Functions like map use typed callbacks:
const numbers = [1, 2, 3];
const strings = numbers.map((n: number) => n.toString());
The callback receives a number, so your transformation is type-checked.
4. Validation logic
You can pass validation functions safely:
type Validator = (input: string) => boolean;
function validateName(name: string, validator: Validator): boolean {
return validator(name);
}
5. Async completion handlers
Even if you later use Promises, older and custom APIs often use callbacks:
type DoneCallback = (success: boolean) => void;
Real Codebase Usage
In real projects, developers rarely use raw Function unless they truly mean "any function at all." Most of the time, they write exact function signatures.
Common patterns
Guarding callback inputs
type SaveCallback = (result: number) => void;
function saveValue(value: number, callback: SaveCallback): void {
if (value < 0) {
return;
}
callback(value);
}
The guard clause handles invalid state early, and the callback stays well typed.
Reusing callback types
type ErrorHandler = (message: string) => void;
type SuccessHandler = (id: number) => void;
Teams often create named callback types for readability.
Passing callbacks into utilities
function processNumbers(
values: number[],
handler: (value: number) => void
): void {
for (const value of values) {
handler(value);
}
}
This pattern appears in utility modules, services, and framework code.
Typed callbacks in configuration objects
type Options = {
onSuccess: (count: number) => void;
onError: (message: string) => void;
};
This is common in libraries and app-level services.
Error-first callback style
Some codebases still use callback conventions like this:
type LoadCallback = (error: Error | null, data: string | null) => void;
Even here, the callback is strongly typed.
Why teams prefer this
- better autocomplete
- clearer APIs
- safer refactoring
- easier code review
- fewer runtime bugs
Common Mistakes
1. Using Function instead of a real signature
Broken:
function save(callback: Function): void {
callback(42);
}
Why it is a problem:
Functionis too generic- parameter and return types are lost
Better:
function save(callback: (result: number) => void): void {
callback(42);
}
2. Forgetting the return type
Sometimes beginners type only the parameter list mentally, but not the full function shape.
Correct form:
(value: number) => void
Not just:
(value: number)
3. Passing a callback with the wrong parameter type
Broken:
const callback = (value: string) => {
console.log(value);
};
save(callback);
Fix:
const callback = (value: number) => {
console.log(value);
};
4. Confusing void with "returns nothing at runtime"
void means the caller should not rely on any returned value.
const cb: (n: number) => void = (n) => {
console.log(n);
};
5. Making callback types harder to read than necessary
Too complex inline types can become messy.
Instead of repeating this everywhere:
(userId: number, isAdmin: boolean, name: string) => void
prefer:
type UserHandler = (userId: number, isAdmin: boolean, name: string) => void;
6. Ignoring compiler settings
Type safety is stronger when your project uses stricter TypeScript settings such as strict. In looser setups, some unsafe cases may be allowed more easily.
Comparisons
| Approach | Example | Type-safe? | Best use |
|---|---|---|---|
Function | callback: Function | No | Rarely useful for app code |
| Inline function type | callback: (n: number) => void | Yes | Simple one-off callbacks |
| Type alias | type SaveCallback = (n: number) => void | Yes | Reusable callback types |
| Interface call signature | interface SaveCallback { (n: number): void } | Yes | Less common, but valid |
Function vs typed function signature
callback: Function
- only says it is callable
- does not describe parameters clearly
- weak for API design
callback: (result: number) => void
- describes exactly what the function must accept
- allows compile-time checking
- preferred in most cases
Type alias vs interface
type SaveCallback = (result: number) => void;
interface SaveCallback {
(result: number): void;
}
Both can work. In many TypeScript codebases, type is more common for simple function signatures because it is short and readable.
TypeScript callback types vs .NET delegates
| Concept | TypeScript | .NET |
|---|---|---|
| Strongly typed callable | (n: number) => void | Action<int> or custom delegate |
| Reusable name | type SaveCallback = ... | delegate void SaveCallback(int n) |
| Compiler checking | Yes | Yes |
So the closest equivalent to a .NET delegate is a typed function signature, often wrapped in a type alias.
Cheat Sheet
// Avoid
callback: Function
// Prefer
callback: (value: number) => void
Common patterns
type Callback = (value: number) => void;
type Formatter = (value: number) => string;
type Validator = (text: string) => boolean;
Class method example
class Foo {
save(callback: (result: number) => void): void {
const result = 42;
callback(result);
}
}
Reusable alias
type SaveCallback = (result: number) => void;
Interface version
interface SaveCallback {
(result: number): void;
}
Rules to remember
Functionis too broad for most callbacks.- Use explicit parameter and return types.
voidmeans callers should ignore the return value.- Prefer type aliases for reusable callback signatures.
- Strict compiler settings improve safety.
Quick mental shortcut
(param1: Type1, param2: Type2) => ReturnType
FAQ
Is Function bad in TypeScript?
Not always, but it is usually too broad for callback parameters. It loses important information about arguments and return values.
How do I type a callback function in TypeScript?
Use a function signature such as:
(callback: (value: number) => void)
Can I create a reusable callback type?
Yes. Use a type alias:
type SaveCallback = (value: number) => void;
Is there a TypeScript equivalent to a C# delegate?
Yes. A typed function signature, often stored in a type alias, is the closest equivalent.
Should I use a type alias or an interface for function types?
Either works. For simple callbacks, many developers prefer type because it is shorter and clearer.
Why did my wrong callback still compile?
It may be because you used Function, or because your TypeScript configuration is not strict enough. Exact behavior can vary with compiler settings.
Can callbacks return values too?
Yes. Example:
type Formatter = (n: number) => string;
This callback takes a number and returns a string.
Mini Project
Description
Build a small TypeScript utility that simulates saving a score and then notifies the caller through a strongly typed callback. This demonstrates how to replace Function with a safe callback signature in a practical, reusable way.
Goal
Create a ScoreService class whose saveScore method accepts only callbacks that take a numeric score.
Requirements
- Create a class named
ScoreService. - Add a method
saveScorethat accepts a callback with onenumberparameter. - Inside the method, simulate saving a score and pass a number to the callback.
- Create one valid callback and call
saveScorewith it. - Add one invalid callback example in a comment to show what TypeScript should reject.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.