Functions are values in JavaScript: you pass them as callbacks, store them in objects and call them with a borrowed this (Functions). TypeScript gives each of those roles a type.
| Syntax | Example | Use it for |
|---|---|---|
| Function type expression | (value: number) => string | Callbacks, function variables |
| Call signature | { (s: string): boolean; message: string } | Callable objects with properties |
| Construct signature | new (x: number) => Point | Classes passed as values |
| Optional, default, rest | (p?: string, g = "Hi", ...n: number[]) | Arguments callers may omit |
| Type parameter | <T>(items: T[]) => T | Relating inputs to outputs |
| this parameter | function (this: Counter) | Functions that rely on this |
Function types and call signatures
A function type expression looks like an arrow function with types. The parameter name is required but plays no part in compatibility. When a function also carries properties, write a call signature inside an object type (note the colon instead of the arrow); prefix it with new for a construct signature, the type of a class.
type Formatter = (value: number) => string;
type Validator = {
message: string; // a property...
(input: string): boolean; // ...plus a call signature
};
const euro: Formatter = (v) => `€${v.toFixed(2)}`; // v: number from context
function isEmail(s: string) { return s.includes("@"); }
isEmail.message = "Enter a valid email address"; // expando property
const check: Validator = isEmail; // OK
const bad: Formatter = (v: string) => v;sigs.ts(11,7): error TS2322: Type '(v: string) => string' is not assignable to type
'Formatter'.
Types of parameters 'v' and 'value' are incompatible.
Type 'number' is not assignable to type 'string'.Property assignments on a function declaration become part of its type, so isEmail satisfies Validator. Avoid the global type Function: calling it returns any.
Optional, default and rest parameters
JavaScript lets callers omit any argument (Parameters); TypeScript does not unless you say so. punct?: string has the type string | undefined, whereas a default value makes the parameter optional and keeps it a plain string in the body. A rest parameter has an array type, and spreading into a fixed parameter list needs a tuple, which as const produces.
function greet(name: string, greeting = "Hello", punct?: string): string {
return `${greeting}, ${name}${punct ?? "!"}`; // punct: string | undefined
}
function sum(first: number, ...rest: number[]): number {
return rest.reduce((acc, n) => acc + n, first);
}
const pair = [8, 5] as const; // readonly [8, 5]
const loose = [8, 5]; // number[]
console.log(greet("Ada"), sum(1, 2, 3), Math.atan2(...pair).toFixed(3));
greet();
Math.atan2(...loose);
const lengths: number[] = [];
["a", "bb"].forEach((s) => lengths.push(s.length)); // push returns a number: OK
function reset(): void { return lengths.length; }params.ts(11,1): error TS2554: Expected 1-3 arguments, but got 0. params.ts(12,12): error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter. params.ts(16,26): error TS2322: Type 'number' is not assignable to type 'void'.
Without the error lines the program prints Hello, Ada! 6 1.012. The last two lines show a deliberate asymmetry: a contextual void return type, like the callback type of forEach, means "the result is ignored", so returning push's number is fine; a function declared : void must not return a value.
Generic functions
When the output type depends on the input type, add a type parameter. TypeScript infers it at each call, and a constraint such as T extends { length: number } limits what callers may pass while letting the body use .length. Use as few type parameters as possible; one that appears only once in the signature is probably unnecessary. Generics covers generics in depth.
function first<T>(items: T[]): T | undefined { return items[0]; }
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
const n = first([3, 1, 2]); // number | undefined
const word = longest("tea", "coffee"); // T is string
longest(10, 20);generic.ts(7,9): error TS2345: Argument of type 'number' is not assignable to parameter of type
'{ length: number; }'.Overloads
Some functions accept genuinely different argument lists. Write two or more overload signatures followed by one implementation signature whose body handles every case. Callers see only the overloads.
function parseDate(timestamp: number): Date;
function parseDate(year: number, month: number, day: number): Date;
function parseDate(iso: string): Date;
function parseDate(a: number | string, month?: number, day?: number): Date {
if (typeof a === "string") return new Date(a);
return month !== undefined && day !== undefined ? new Date(a, month - 1, day) : new Date(a);
}
console.log(parseDate(2026, 9, 15).getMonth()); // 8 (months count from 0)
parseDate(2026, 9);
declare const input: number | string;
parseDate(input);overload.ts(10,1): error TS2575: No overload expects 2 arguments, but overloads do exist that
expect either
1 or 3 arguments.
overload.ts(12,11): error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type 'string | number' is not assignable to parameter of type 'string'.
Type 'number' is not assignable to type 'string'.The second error is the classic pitfall: each call is checked against one overload at a time, so a number | string argument matches none of them although the implementation would accept it. Prefer a union parameter when the result type is the same; keep overloads for when arguments decide the result type, as in createElement, which returns HTMLCanvasElement for "canvas" but HTMLElement for an unknown tag.
Declaring this
In a function (not an arrow), this depends on how the function is called (prototype, this and new). Declare its type as a fake first parameter named this: it is erased from the JavaScript output, and every call site is checked. Under noImplicitThis, part of strict, an untyped this outside a method is an error.
interface Counter { count: number; step: number }
function increment(this: Counter, times = 1): number {
this.count += this.step * times;
return this.count;
}
const counter = { count: 0, step: 5, increment };
counter.increment(2); // OK: this is counter
const detached = counter.increment;
detached();
document.querySelector("button")?.addEventListener("click", function () {
this.disabled = true; // this: HTMLButtonElement
});
function legacy() { return this.count; }this.ts(9,1): error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type 'Counter'. this.ts(14,28): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
The DOM library declares listeners with a this: HTMLButtonElement parameter, so a function handler gets a typed this for free. An arrow function captures the surrounding this instead.