TypeScript Fundamentals: Types, Generics and Real-World Patterns · TypeScript Basics: Types, Narrowing and Functions
Functions: Parameters, Callbacks and Overloads
Type function parameters and return values, use optional, default and rest parameters, describe callbacks with function types, and write overloads for functions with several call shapes.
What you'll build
A tiny text toolkit: a slugify function with options, a formatList function with rest parameters, a retry helper that takes a callback, and an overloaded parse function that returns a different type depending on its argument.
The idea
Function signatures are where TypeScript pays off the most, because they document how code is used and stop wrong calls.
- Parameters and return type:
function add(a: number, b: number): number. TypeScript infers return types, but writing them for exported functions catches accidents. - Optional (
?) and default (= value) parameters. Optional parameters must come last and areT | undefinedinside the function. - Rest parameters:
function sum(...nums: number[])collects extra arguments into an array. - Function types describe callbacks:
(value: string, index: number) => boolean. Name them withtype Predicate = (value: string) => boolean. voidmeans "the return value is not used".nevermeans the function never returns (it throws or loops forever).- Overloads give a function several call signatures: write each signature, then one implementation signature that accepts all of them.
Context-sensitive typing means arrow functions passed to typed parameters need no annotations. In ["a","b"].map((s) => s.length), s is inferred as string.
The code
interface SlugOptions {
separator?: string;
maxLength?: number;
}
function slugify(text: string, { separator = "-", maxLength = 30 }: SlugOptions = {}): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, separator)
.replace(new RegExp(`^${separator}|${separator}$`, "g"), "")
.slice(0, maxLength);
}
console.log(slugify("Hello, TypeScript World!"));
console.log(slugify("Hello, TypeScript World!", { separator: "_", maxLength: 12 }));
// Rest parameters
function formatList(label: string, ...items: string[]): string {
return `${label}: ${items.join(", ") || "(none)"}`;
}
console.log(formatList("Tags", "ts", "js"));
console.log(formatList("Empty"));
// Callbacks with a named function type
type Task<T> = () => T;
function retry<T>(task: Task<T>, attempts: number): T {
let lastError: unknown;
for (let i = 1; i <= attempts; i++) {
try {
return task();
} catch (error) {
lastError = error;
console.log(`attempt ${i} failed`);
}
}
throw lastError;
}
let calls = 0;
const result = retry(() => {
calls++;
if (calls < 3) throw new Error("flaky");
return `succeeded after ${calls} calls`;
}, 5);
console.log(result);
// Overloads: the return type depends on the argument type
function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[] {
return Array.isArray(input) ? input.map(Number) : Number(input);
}
const one = parse("42"); // number
const many = parse(["1", "2"]); // number[]
console.log(one + 1, many.map((n) => n * 2));
Output
hello-typescript-world hello_typesc Tags: ts, js Empty: (none) attempt 1 failed attempt 2 failed succeeded after 3 calls 43 [ 2, 4 ]
Common mistakes
- Marking a parameter optional but using it unguarded.
maxLength?: numbermay beundefined; give it a default or check it. - Using
Functionas a type. It accepts any function and gives no information. Write the signature:(x: number) => string. - Writing overloads that the implementation can't satisfy. The implementation signature must accept every overload, and callers can't see it.
- Needing many overloads where a generic or union would do. Prefer
function first<T>(items: T[]): T | undefined. - Returning different shapes from the same function. A caller then has to guess; use a union with a discriminant.
Key points
- Annotate parameters, and annotate returns for exported functions; inference handles callbacks.
- Optional (
?), default (=) and rest (...) parameters make signatures flexible and typed. - Function types such as
(x: string) => booleandescribe callbacks;voidignores the result andnevernever returns. - Overloads declare several call signatures backed by one implementation.
Further reading: The Handbook: More on Functions.
Practice questions
Why must optional parameters come after required ones?
Answer
Arguments are matched by position, so a missing argument can only be left out at the end. An optional parameter in the middle would make it impossible to tell which arguments were supplied.
What is the implementation signature of an overloaded function, and who can see it?
Answer
It is the single signature with the function body that must be compatible with every overload. Callers cannot call it directly; they only see the overload signatures above it.
Open in the app to mark lessons complete, take notes and track your progress.