Functions
Typing a function
Section titled “Typing a function”A function type describes parameters and a return type. You can write it inline or as a named type:
type BinaryOp = (a: number, b: number) => number;
const add: BinaryOp = (a, b) => a + b;// a and b are inferred as number from the context — no annotations neededParameters support optional (?), default values, and rest:
function make(name: string, count = 1, ...tags: string[]): void { // count defaults to 1; tags collects the rest into string[]}An optional parameter x?: number is number | undefined; a defaulted parameter count = 1 is simply number from the callee’s view, but callers may omit it.
Overloads — and why a union is often better
Section titled “Overloads — and why a union is often better”Overloads let one function present multiple call signatures. You write several signature lines followed by one implementation:
function len(x: string): number;function len(x: unknown[]): number;function len(x: string | unknown[]): number { return x.length;}
len("hi"); // ✅ 2len([1, 2, 3]); // ✅ 3Overloads are powerful but easy to overuse. When the signatures differ only in a parameter type — not in the relationship between parameters and return — a single union parameter is simpler and clearer:
// Simpler than two overloads:function len(x: string | unknown[]): number { return x.length;}Reach for real overloads only when the return type genuinely depends on which input shape you got in a way a union can’t express.
Typing this
Section titled “Typing this”In a standalone function, this can be typed with a special first parameter that disappears at the call site (it’s compile-time only):
interface Button { label: string; }
function handleClick(this: Button, event: Event): void { console.log(this.label); // this is typed as Button}This catches the classic bug of a method losing its this when passed as a bare callback. It’s most useful in older this-based APIs; with arrow functions and modern patterns you’ll need it less.
The void return rule that surprises everyone
Section titled “The void return rule that surprises everyone”A function type that returns void does not mean “the function must return nothing.” It means “the caller will ignore any return value.” So a function that does return something is still assignable to a void-returning type:
type Callback = () => void;
const cb: Callback = () => 42; // ✅ allowed! the 42 is just ignoredflowchart LR fn["() => number (returns 42)"] -->|assignable to| target["() => void (caller ignores return)"] target --> use["callers treat result as void — safe"]
This is exactly why array.forEach(x => arr.push(x)) type-checks even though push returns a number: forEach wants a void callback and simply ignores whatever comes back. Once you know the rule it stops being mysterious — but almost everyone hits it confused first.