Functions

This section documents the current TypeScript release line as published at the official TypeScript documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since TypeScript iterates quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

Functions are where most type annotations live in day-to-day TypeScript. This page covers annotating parameters and returns, the special parameter forms, the void/never return types, this typing, the object forms of a function type (call and construct signatures, overloads), and how parameters get their types for free from context. The authoritative reference is More on Functions.

Parameter and return annotations

Annotate every parameter. Let TypeScript infer the return type unless you have a reason to pin it.

function add(a: number, b: number): number {
  return a + b;
}

// Return type inferred as `string` -- no annotation needed
function greet(name: string) {
  return `Hello, ${name}`;
}

State the return type explicitly to check the body against a contract (a wrong return is then flagged at the function, not at the call site), to stop the inferred type widening or getting noisier than intended, or to break a circular inference. Otherwise inference keeps the annotation from drifting out of sync with the code.

Typing the whole function expression

For a function expression, put the type on the binding with a type alias instead of annotating each piece. Parameters and the return type are then contextually typed (see below), so the body needs no inline annotations.

type BinaryOp = (a: number, b: number) => number;

const multiply: BinaryOp = (a, b) => a * b;       // a, b, and the return are all inferred from BinaryOp
const subtract: BinaryOp = (a, b) => a - b;

This is the idiomatic way to give several functions the same shape, and it pairs well with generic type aliases when the shape is parameterised.

Optional, default, and rest parameters

A ? marks a parameter optional (its type becomes T | undefined); a default value makes it optional at the call site while keeping the narrow type in the body; a …​rest collects the remaining arguments into an array or tuple.

function createLink(url: string, text?: string): string {
  return `<a href="${url}">${text ?? url}</a>`;   // text: string | undefined
}

function repeat(value: string, times = 1): string {
  return value.repeat(times);                      // times: number, never undefined
}

function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

// A tuple rest type fixes arity and per-position types
function labelledPoint(...coords: [x: number, y: number, label?: string]) {
  return coords;
}

Optional parameters must follow required ones. Spreading an argument list into a call also relies on the callee’s rest or tuple type to check it.

Destructuring parameters with types

Annotate the pattern with an object type. The names inside the braces are bindings, not type members, so the type annotation goes after the closing brace.

function drawRect({ width, height, color = "black" }: {
  width: number;
  height: number;
  color?: string;
}): void {
  console.log(width, height, color);
}

Prefer an options object over repeated same-type parameters

Several parameters of the same type are easy to pass in the wrong order and the compiler cannot catch it. Pass a single object so each value is named at the call site.

// Hard to call correctly: which number is which?
function makeRange(start: number, end: number, step: number): number[] { /* ... */ return []; }

// Self-documenting, order-independent, easy to extend
interface RangeOptions {
  start: number;
  end: number;
  step?: number;
}
function makeRange2({ start, end, step = 1 }: RangeOptions): number[] { /* ... */ return []; }

makeRange2({ start: 0, end: 10, step: 2 });

See Objects and Interfaces for modelling these option bags.

void and never returns

void means the caller should ignore whatever is returned. A key rule: a function type that returns void accepts an implementation that returns any value — so callbacks like Array.prototype.forEach can be passed functions that happen to return something, and that value is simply discarded.

type Logger = (message: string) => void;

const log: Logger = (message) => console.log(message); // returns number; allowed because Logger returns void

const values: number[] = [];
// push returns the new length; the void return type of forEach's callback makes that harmless
[1, 2, 3].forEach((n) => values.push(n));

never is the type of a function that never returns normally — it always throws or loops forever. It is not the same as void.

function fail(message: string): never {
  throw new Error(message);
}

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);   // exhaustiveness check in switch statements
}

this parameters

TypeScript lets you declare the type of this as a fake first parameter named this. It is erased at compile time and is not a real argument. See Declaring this in a Function.

interface Counter {
  count: number;
  increment(this: Counter, by: number): void;
}

const counter: Counter = {
  count: 0,
  increment(by) {
    this.count += by;                              // this is typed as Counter
  },
};

Arrow functions have no this of their own, so they cannot take a this parameter — use a regular function when the callee will call it with a specific receiver. For callbacks invoked by a library (DOM handlers, jQuery plugins), type this in the callback so the body sees the right receiver:

function onClick(this: HTMLButtonElement, ev: MouseEvent): void {
  this.disabled = true;                            // this is HTMLButtonElement, not any
}
document.querySelector("button")?.addEventListener("click", onClick);

Call signatures, construct signatures, and overloads

An object type can describe something callable by giving it a call signature, and it can carry properties alongside. Add the new keyword for a construct signature — a value invoked with new.

// Call signature plus a property
type Predicate = {
  (value: unknown): boolean;
  description: string;
};

const isEven: Predicate = Object.assign(
  (value: unknown) => typeof value === "number" && value % 2 === 0,
  { description: "true for even numbers" },
);

// Construct signature: a value invoked with `new`
type DateFactory = {
  new (value: number): Date;
};

Function overloads

An overloaded function has several call signatures followed by one implementation signature. The implementation signature is not visible to callers and must be compatible with all overloads. See Function Overloads.

function parseInput(value: string): string[];
function parseInput(value: number): number[];
function parseInput(value: string | number): string[] | number[] {
  return typeof value === "string" ? value.split(",") : [value];
}

const a = parseInput("a,b,c"); // string[]
const b = parseInput(42);      // number[]

Prefer a union, a conditional type, or generics to overloads

Overloads are verbose, easy to get subtly wrong, and give poor errors on a bad call. Reach for them only when the return type genuinely depends on discrete input shapes in a way the alternatives cannot express.

// Instead of two overloads, one signature with a union parameter
function formatId(id: string | number): string {
  return typeof id === "string" ? id : `#${id}`;
}

// Return type follows the argument type via a conditional type
function wrap<T>(value: T): T extends unknown[] ? T : T[] {
  return (Array.isArray(value) ? value : [value]) as never;
}

// A generic keeps the caller's exact type without listing every case
function first<T>(items: readonly T[]): T | undefined {
  return items[0];
}

Generics covers the last two patterns in depth.

Contextual typing

When an expression’s expected type is known, TypeScript flows that type into the expression — callback parameters get typed without any annotation. This is why inline callbacks rarely need : T on their parameters.

const names = ["Ada", "Alan", "Grace"];

names.forEach((name) => {
  console.log(name.toUpperCase());                 // name: string, inferred from names' element type
});

window.addEventListener("keydown", (event) => {
  console.log(event.key);                          // event: KeyboardEvent, from the listener map
});

// Object literal in a typed position: each method's parameters are contextually typed
type Handlers = { onSave: (id: number) => void; onDelete: (id: number) => void };
const handlers: Handlers = {
  onSave: (id) => console.log("save", id),         // id: number
  onDelete: (id) => console.log("delete", id),
};

Contextual typing also drives the async callbacks and iteration helpers described in Async, Iterators and Generators.