|$ curl https://forge-ai.dev/api/markdown?path=docs/typescript/functions
$cat docs/typescript-—-function-types.md
updated Recently·14 min read·published

TypeScript — Function Types

TypeScriptIntermediate
Introduction

Functions are the core building blocks of any TypeScript program. TypeScript enhances JavaScript functions with parameter type annotations, return type annotations, overloads, and this parameter typing. Understanding function types is essential for writing robust, self-documenting code.

Function Type Annotations

TypeScript lets you annotate both parameter types and return types. Return types are often inferred, but explicit annotations catch bugs at the call site.

annotations.ts
TypeScript
1// Function declaration with annotations
2function add(a: number, b: number): number {
3 return a + b;
4}
5
6// Arrow function
7const multiply = (a: number, b: number): number => a * b;
8
9// Return type is inferred — but explicit is often better
10function greet(name: string): string {
11 return `Hello, ${name}!`;
12}
13
14// void return — function has no meaningful return value
15function log(message: string): void {
16 console.log(message);
17}
18
19// implicit return type: void
20function noop(): void {
21 return undefined;
22}
23
24// never — function never returns (throws or loops forever)
25function throwError(msg: string): never {
26 throw new Error(msg);
27}
28
29function infiniteLoop(): never {
30 while (true) {}
31}
Optional, Default, and Rest Parameters

TypeScript supports optional parameters (suffix ?), default parameters, and rest parameters (prefix ...). Optional parameters must come after required ones.

params.ts
TypeScript
1// Optional parameter
2function buildUrl(base: string, path?: string): string {
3 return path ? `${base}/${path}` : base;
4}
5console.log(buildUrl("https://api.com")); // https://api.com
6console.log(buildUrl("https://api.com", "users")); // https://api.com/users
7
8// Default parameter
9function createUser(
10 name: string,
11 role: string = "viewer",
12 active: boolean = true
13): object {
14 return { name, role, active };
15}
16createUser("Alice"); // { name: "Alice", role: "viewer", active: true }
17createUser("Bob", "admin"); // { name: "Bob", role: "admin", active: true }
18createUser("Carol", "editor", false);
19
20// Rest parameter — collects arguments into an array
21function sum(...numbers: number[]): number {
22 return numbers.reduce((total, n) => total + n, 0);
23}
24sum(1, 2, 3); // 6
25sum(10, 20, 30, 40); // 100
26
27// Rest after required params
28function logWithPrefix(prefix: string, ...messages: string[]): void {
29 messages.forEach((msg) => console.log(`[${prefix}] ${msg}`));
30}
31logWithPrefix("INFO", "connected", "ready");
32
33// Combined: required, optional, default, rest
34function request(
35 url: string,
36 method: string = "GET",
37 options?: object,
38 ...middleware: Array<(req: object) => object>
39): object {
40 let req: object = { url, method, ...options };
41 return middleware.reduce((acc, fn) => fn(acc), req);
42}

info

Optional parameters in TypeScript are implicitly | undefined. You can narrow the type using a guard: if (path !== undefined).
Call Signatures

When you need to describe the type of a function that also has properties, use a call signature in an object type. This is common for callback-based APIs and factory functions.

call_signatures.ts
TypeScript
1// Simple function type alias
2type Formatter = (input: string) => string;
3
4// Call signature — function type with additional properties
5interface DescribableFunction {
6 (input: string): string;
7 description: string;
8}
9
10function createFormatter(desc: string): DescribableFunction {
11 const fn = (input: string) => input.toUpperCase();
12 fn.description = desc;
13 return fn;
14}
15
16const shout: DescribableFunction = createFormatter("Shout formatter");
17console.log(shout("hello")); // HELLO
18console.log(shout.description); // Shout formatter
19
20// Constructor signature
21interface ClockConstructor {
22 new (hour: number, minute: number): ClockInterface;
23}
24
25interface ClockInterface {
26 tick(): void;
27}
28
29class DigitalClock implements ClockInterface {
30 constructor(private h: number, private m: number) {}
31 tick(): void {
32 console.log(`beep beep ${this.h}:${this.m}`);
33 }
34}
35
36function createClock(
37 ctor: ClockConstructor,
38 hour: number,
39 minute: number
40): ClockInterface {
41 return new ctor(hour, minute);
42}
43
44createClock(DigitalClock, 12, 0).tick(); // beep beep 12:0
this Parameters

TypeScript uses a fake this parameter to type the this context. The first parameter named this is stripped at runtime but used for type checking.

this.ts
TypeScript
1interface UIElement {
2 addClickListener(
3 this: UIElement,
4 handler: (this: UIElement, event: Event) => void
5 ): void;
6}
7
8class Button implements UIElement {
9 private listeners: Array<(event: Event) => void> = [];
10
11 addClickListener(
12 this: Button,
13 handler: (this: Button, event: Event) => void
14 ): void {
15 this.listeners.push((e) => handler.call(this, e));
16 }
17}
18
19// Without this parameter — loses context
20const btn = new Button();
21const handler = function (this: Button, event: Event) {
22 console.log("clicked!");
23};
24
25// Common pattern: ensure method is called on correct object
26function assertDefined<T>(this: T, field: keyof T): void {
27 if (this[field] === undefined) {
28 throw new Error(`${String(field)} is required`);
29 }
30}
31
32const obj = { name: "Alice", age: undefined as number | undefined };
33// assertDefined.call(obj, "name"); // OK
34// assertDefined.call(obj, "age"); // Error at runtime: age is required

warning

Without a this parameter, TypeScript cannot warn you when a method is detached from its object. Always use this parameters in classes and object-oriented patterns.
Function Overloads (Brief)

TypeScript allows multiple overload signatures for a single function implementation. Each overload is a different call pattern with its own types.

overloads_intro.ts
TypeScript
1// Overload signatures — what callers see
2function makeDate(timestamp: number): Date;
3function makeDate(year: number, month: number, day: number): Date;
4
5// Implementation signature — the actual body
6function makeDate(
7 yearOrTimestamp: number,
8 month?: number,
9 day?: number
10): Date {
11 if (month !== undefined && day !== undefined) {
12 return new Date(yearOrTimestamp, month - 1, day);
13 }
14 return new Date(yearOrTimestamp);
15}
16
17makeDate(1700000000000); // Date — timestamp overload
18makeDate(2024, 1, 15); // Date — year/month/day overload
19// makeDate(2024, 1); // Error: no matching overload

info

Keep overload signatures minimal and clear. Detailed coverage of overloads is in the next page.
void vs undefined vs never
void_never.ts
TypeScript
1// void — function does not return a value
2function warn(msg: string): void {
3 console.warn(msg);
4 // no return statement (or returns undefined)
5}
6
7// undefined — function explicitly returns undefined
8function returnUndefined(): undefined {
9 return undefined;
10}
11
12// never — function never returns at all
13function throwError(msg: string): never {
14 throw new Error(msg);
15}
16
17function infinite(): never {
18 while (true) {}
19}
20
21// Exhaustive check pattern using never
22type Shape =
23 | { kind: "circle"; radius: number }
24 | { kind: "rectangle"; width: number; height: number };
25
26function area(shape: Shape): number {
27 switch (shape.kind) {
28 case "circle":
29 return Math.PI * shape.radius ** 2;
30 case "rectangle":
31 return shape.width * shape.height;
32 default:
33 const _exhaustive: never = shape; // compile error if missing case
34 return _exhaustive;
35 }
36}
37
38// void in callback position — return value is ignored
39function forEach<T>(arr: T[], callback: (item: T) => void): void {
40 for (const item of arr) {
41 callback(item);
42 }
43}
44
45// Assigning void-typed functions:
46// A function returning void can still return a value,
47// but callers are not allowed to use it.
48function doSomething(): void {
49 return 42; // OK! But callers can't use the value.
50}

best practice

Use never for exhaustive switch/if-else patterns. When you add a new union member, the compiler forces you to handle it everywhere.
Best Practices

1. Always annotate return types on exported functions — they serve as documentation and catch accidental changes.

2. Use this parameters in class methods and callbacks to prevent detached-context bugs.

3. Prefer default parameters over optional parameters when a sensible fallback exists — it simplifies the call site.

4. Rest parameters are preferable to arguments — they are typed and create a proper array.

5. Use never return type for exhaustive checks in switch statements — this pattern catches missing cases at compile time.

6. Keep function overloads minimal. If you find yourself writing many overloads, consider generics or union types instead.

7. Use call signatures (interface with ()) when the function needs additional properties. Otherwise, a simple function type alias suffices.

8. Avoid void as a return type on generic callbacks — it prevents callers from using the return value even when it is meaningful.

$Blueprint — Engineering Documentation·Section ID: TS-FN·Revision: 1.0