Function Types
A function’s type captures two things: the types of its parameters and the type of the value it returns. TypeScript can infer much of this from the implementation, but you will often want to declare the type explicitly — to document a callback, share a signature across implementations, or constrain a value passed as an argument. This page covers how to annotate parameters and return values, write standalone function type expressions, and let inference do the rest where it is safe.
Annotating parameters and return types
The most direct way to type a function is to annotate each parameter and the return value where it is declared. Parameter annotations come after the name; the return annotation follows the parameter list.
function add(a: number, b: number): number {
return a + b;
}
const greet = (name: string): string => `Hello, ${name}`;
Every call site is now checked against this signature. Passing a string where a number is expected, or calling add with the wrong number of arguments, is a compile error rather than a silent runtime surprise.
Letting return types be inferred
You rarely need to annotate the return type — TypeScript infers it from the return statements. Inference keeps code concise and stays correct as the body evolves. An explicit return type is still useful when you want the compiler to verify your intent, or to lock a public API.
// Inferred as `number`
function square(n: number) {
return n * n;
}
// Explicit return type guards against accidental shape changes
function loadUser(id: string): { id: string; name: string } {
return { id, name: "Ada" };
}
Annotate return types on exported, public-facing functions. It makes the contract explicit, produces clearer error messages at the definition rather than the call site, and prevents an unrelated edit from silently widening the API.
Function type expressions
A function type expression describes a function’s shape without an implementation, using the (params) => ReturnType syntax. It is the idiomatic way to type callbacks and higher-order parameters.
type Transformer = (input: string) => string;
const shout: Transformer = (text) => text.toUpperCase();
function mapStrings(values: string[], fn: (item: string) => string): string[] {
return values.map(fn);
}
Note that the parameter names inside a function type (input, item) are purely documentation — only their types and positions matter. When a function is assigned to a typed variable, its parameter types are inferred from the annotation (contextual typing), so text above is known to be a string.
Void return types
A return type of void means the caller should not rely on a returned value. TypeScript treats void specially in callbacks: a function typed to return void may still return a value, and that value is ignored. This is what lets Array.prototype.forEach accept callbacks that happen to return something.
type Logger = (message: string) => void;
const log: Logger = (message) => message.length; // ✅ extra return value ignored
const numbers: number[] = [];
[1, 2, 3].forEach((n) => numbers.push(n)); // push returns number; that's fine
This rule only applies to the void return position of a function type. A variable annotated directly as void cannot hold a meaningful value.
Comparing the ways to type a function
| Form | Example | Use when |
|---|---|---|
| Inline annotation | function f(a: number): number | Declaring a concrete function |
| Inferred return | function f(a: number) { ... } | Letting TS derive the return type |
| Type expression | type F = (a: number) => number | Typing callbacks/parameters |
| Call signature | interface F { (a: number): number } | Function with extra properties |
For functions that also carry properties (or that you want to overload), reach for a call signature instead of a type expression — see the dedicated pages below.
Optional and rest parameters in types
Function type expressions support the same parameter modifiers as declarations: ? for optional parameters and ... for a rest parameter. Optional parameters must follow required ones, and a rest parameter must come last.
type Handler = (event: string, payload?: unknown) => void;
type Variadic = (...args: number[]) => number;
const sum: Variadic = (...args) => args.reduce((a, b) => a + b, 0);
These modifiers are covered in depth on the optional, default, and rest parameter pages.
Best Practices
- Annotate parameters explicitly; let return types be inferred unless the function is part of a public API.
- Use function type expressions (
(x: T) => R) to type callbacks and higher-order parameters. - Add explicit return types to exported functions to lock the contract and improve error locality.
- Prefer precise parameter types over
any; reach forunknownwhen the input is genuinely untyped. - Use
voidfor callbacks whose return value is irrelevant, and rely on the assignability rule forforEach-style APIs. - Keep optional parameters after required ones and the rest parameter last.
- Use a call signature instead of a type expression when the function also needs properties or overloads.