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
function add(a: number, b: number): number { return a + b; } console.log(add(3, 4)); console.log(add("3", 4));
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
const square = (n: number): number => n * n; const isEven = function (n: number): boolean { return n % 2 === 0; }; console.log(square(6), isEven(7));
36 false
The syntax is the same everywhere: annotate each parameter, and optionally the return type after the parameter list.
Optional and default parameters
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"));
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:
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");
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:
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 }));
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:
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));
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:
const names: string[] = []; const incoming = ["a", "b", "c"]; incoming.forEach(n => names.push(n)); console.log(names);
[ '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:
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);
Save clicked 1 time(s)