Functions

Typing a function means annotating its parameters and, usually, its return value — so a mistake in what a function is called with, or what it hands back, is caught like any other type mismatch. TypeScript also lets you describe functions themselves as types.

Parameter and return types

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

console.log(add(3, 4));
console.log(add("3", 4));
tsc output
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

The : number after the closing parenthesis is the return type. If the body ever returned something else, that line would fail to compile. Calling with too many or too few arguments is also an error — unlike JavaScript, which silently ignores extras and fills missing ones with undefined.

Arrow functions and function expressions

TS arrows.ts
const square = (n: number): number => n * n;

const isEven = function (n: number): boolean {
  return n % 2 === 0;
};

console.log(square(6), isEven(7));
Output
36 false

The syntax is the same everywhere: annotate each parameter, and optionally the return type after the parameter list.

Optional and default parameters

TS defaults.ts
function greet(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

function tag(text: string, category?: string): string {
  return category ? `[${category}] ${text}` : text;
}

console.log(greet("Sam"));
console.log(greet("Sam", "Hey"));
console.log(tag("Server down"));
console.log(tag("Server down", "urgent"));
Output
Hello, Sam!
Hey, Sam!
Server down
[urgent] Server down

A default value (greeting = "Hello") is used whenever the caller leaves that argument out, and its type is inferred from the default. category? is optional with no default — inside the function it's string | undefined. Optional parameters must come after required ones.

Rest parameters

A rest parameter collects any number of arguments into an array, and its type is written as an array type:

TS rest.ts
function sum(...values: number[]): number {
  return values.reduce((total, v) => total + v, 0);
}

function log(level: string, ...parts: (string | number)[]) {
  console.log(`[${level}]`, ...parts);
}

console.log(sum(1, 2, 3, 4));
log("info", "user", 42, "logged in");
Output
10
[info] user 42 logged in

Destructured parameters

When a function takes an options object, the type goes after the whole destructuring pattern, not inside it:

TS destructured.ts
type ResizeOptions = { width: number; height: number; keepRatio?: boolean };

function resize({ width, height, keepRatio = true }: ResizeOptions) {
  return `${width}x${height}${keepRatio ? " (ratio locked)" : ""}`;
}

console.log(resize({ width: 800, height: 600 }));
Output
800x600 (ratio locked)

A common mistake is writing { width: number } inside the pattern — in destructuring, that syntax renames width to a variable called number.

Function types

Functions are values, so they have types too. A function type looks like an arrow function with types instead of code:

TS callback.ts
type NumberOp = (n: number) => number;

function applyTwice(value: number, fn: NumberOp): number {
  return fn(fn(value));
}

const addTen: NumberOp = n => n + 10;
console.log(applyTwice(5, addTen));
console.log(applyTwice(3, n => n * n));
Output
25
81

Parameter names in a function type are just labels; the implementation can use different names. Notice that n in the arrow functions needed no annotation — TypeScript infers it from NumberOp. This contextual typing is why callbacks passed to map, filter and event handlers rarely need annotations.

void callbacks

A callback typed as returning void is allowed to return a value; the value is just ignored. That's what makes this common pattern legal:

TS void-callback.ts
const names: string[] = [];
const incoming = ["a", "b", "c"];

incoming.forEach(n => names.push(n));
console.log(names);
Output
[ 'a', 'b', 'c' ]

push returns a number, but forEach expects a void callback, and TypeScript allows it because nobody reads the result.

Typing this

If a function uses this, you can declare its type as a fake first parameter. It's removed at compile time:

TS this.ts
type Button = { label: string; clicks: number };

function handleClick(this: Button) {
  this.clicks++;
  console.log(`${this.label} clicked ${this.clicks} time(s)`);
}

const btn: Button = { label: "Save", clicks: 0 };
handleClick.call(btn);
Output
Save clicked 1 time(s)

Try it yourself

Try it yourself
Console output