Functions
Functions are where TypeScript pays off the most. A typed function tells every caller exactly what to pass in and what comes back, and the compiler checks every call in your whole project. If you later change the function, TypeScript shows you every place that needs updating.
In this lesson you will type parameters and return values, use optional, default and rest parameters, describe functions as types for callbacks, and finish with function overloads.
Parameter and return types
Annotate each parameter with a type. You can also annotate the return type after the parameter list. If you leave the return type out, TypeScript infers it from the return statements.
function gstAmount(price: number, ratePercent: number): number {
return Math.round((price * ratePercent) / 100);
}
function formatRupees(amount: number): string {
return "₹" + amount.toLocaleString("en-IN");
}
function printReceipt(item: string, price: number): void {
const gst = gstAmount(price, 18);
console.log(`${item}: ${formatRupees(price)} + GST ${formatRupees(gst)}`);
}
printReceipt("Laptop bag", 1299);
printReceipt("Office chair", 15499);Laptop bag: ₹1,299 + GST ₹234 Office chair: ₹15,499 + GST ₹2,790
void means the function does not return a useful value. Writing return types is optional, but it is a good habit for exported functions: the compiler then checks the function body against your intention, and readers see the result type without reading the code.
The compiler checks every call and every return:
function average(marks: number[]): number {
if (marks.length === 0) {
return "no marks";
}
return marks.reduce((a, b) => a + b, 0) / marks.length;
}
average([80, 90], 2);
average();app.ts(3,5): error TS2322: Type 'string' is not assignable to type 'number'. app.ts(8,19): error TS2554: Expected 1 arguments, but got 2. app.ts(9,1): error TS2554: Expected 1 arguments, but got 0.
Unlike JavaScript, TypeScript does not let you pass too many or too few arguments.
Optional, default and rest parameters
A parameter followed by ? is optional. Inside the function its type includes undefined, so you must handle the missing case. A default value makes a parameter optional too, and its type is inferred from the default. A rest parameter collects any number of arguments into an array.
function greet(name: string, title?: string): string {
if (title) {
return `Namaste, ${title} ${name}`;
}
return `Namaste, ${name}`;
}
function ticketPrice(base: number, quota = "general"): number {
return quota === "tatkal" ? base + 400 : base;
}
function totalRuns(batter: string, ...scores: number[]): string {
const sum = scores.reduce((a, b) => a + b, 0);
return `${batter}: ${sum} runs in ${scores.length} innings`;
}
console.log(greet("Meera"));
console.log(greet("Sharma", "Dr."));
console.log(ticketPrice(1200), ticketPrice(1200, "tatkal"));
console.log(totalRuns("Rohan", 45, 12, 88, 0));Namaste, Meera Namaste, Dr. Sharma 1200 1600 Rohan: 145 runs in 4 innings
Optional parameters must come after the required ones. The parameter quota has the inferred type string because its default is a string.
Function types and callbacks
Functions are values in JavaScript, so they need types too. A function type looks like an arrow function without a body: (price: number) => number means "a function that takes a number and returns a number". Give it a name with type when you use it more than once.
type PriceRule = (price: number) => number;
const festiveSale: PriceRule = (price) => price * 0.8;
const flatOff: PriceRule = (price) => Math.max(price - 200, 0);
const noDiscount: PriceRule = (price) => price;
function applyRule(prices: number[], rule: PriceRule): number[] {
return prices.map(rule);
}
const cart = [999, 450, 1500];
console.log(applyRule(cart, festiveSale));
console.log(applyRule(cart, flatOff));
console.log(applyRule(cart, noDiscount));
const names = ["kabir", "isha", "farhan"];
console.log(names.map((n) => n.charAt(0).toUpperCase() + n.slice(1)));[ 799.2, 360, 1200 ] [ 799, 250, 1300 ] [ 999, 450, 1500 ] [ 'Kabir', 'Isha', 'Farhan' ]
Notice that (price) => price * 0.8 has no annotation on price. TypeScript already knows it must be a number from the PriceRule type. This is called contextual typing: the place where a function is used tells TypeScript its parameter types. The same thing happens in the last line, where n is known to be a string because names is a string[].
A callback type with a void return accepts functions that return something, which is why you can pass (x) => list.push(x) to forEach. The returned value is simply ignored.
Function overloads
Sometimes one function accepts different kinds of input and returns different types for each. Overloads let you write several signatures for one function. You list the signatures callers can use, then write one implementation that handles all of them.
function parseMarks(input: string): number[];
function parseMarks(input: number[]): string;
function parseMarks(input: string | number[]): number[] | string {
if (typeof input === "string") {
return input.split(",").map((part) => Number(part.trim()));
}
return input.join(", ");
}
const list = parseMarks("78, 91, 64"); // type: number[]
const text = parseMarks([55, 82]); // type: string
console.log(list, list.length);
console.log(text.toUpperCase());[ 78, 91, 64 ] 3 55, 82
Callers see only the first two signatures, so parseMarks("1,2") is known to return number[] and parseMarks([1, 2]) to return string. The third signature belongs to the implementation and is hidden from callers.
Common mistakes
- Leaving parameters without types. In strict mode this is error
TS7006: Parameter 'x' implicitly has an 'any' type. Annotate every parameter of a normal function. - Putting an optional parameter first.
function f(a?: string, b: number)is an error; required parameters come first. - Using an optional parameter without checking it.
title.toUpperCase()fails whentitleisundefined. Check it first or give it a default. - Annotating callback parameters that are already known. In
names.map((n: string) => ...)the annotation is redundant; contextual typing does it.
Key points
- Annotate parameters; return types are inferred but are worth writing on exported functions.
voidmarks a function with no useful return value.param?is optional,param = valuehas a default, and...rest: T[]collects extra arguments.- Function types such as
(price: number) => numberdescribe callbacks, and contextual typing fills in callback parameter types. - Overloads give one function several typed signatures; prefer unions or generics when they are enough.
Exercise
Write a type Validator = (value: string) => string | null that returns an error message or null. Create two validators: notEmpty and isPincode (exactly 6 digits). Then write validate(value: string, ...rules: Validator[]): string[] that returns all error messages. Test it with "", "1100" and "110001".
Show solution
type Validator = (value: string) => string | null;
const notEmpty: Validator = (value) => (value.trim() === "" ? "Required" : null);
const isPincode: Validator = (value) => (/^\d{6}$/.test(value) ? null : "Pincode must be 6 digits");
function validate(value: string, ...rules: Validator[]): string[] {
const errors: string[] = [];
for (const rule of rules) {
const message = rule(value);
if (message !== null) errors.push(message);
}
return errors;
}
console.log(validate("", notEmpty, isPincode)); // [ 'Required', 'Pincode must be 6 digits' ]
console.log(validate("1100", notEmpty, isPincode)); // [ 'Pincode must be 6 digits' ]
console.log(validate("110001", notEmpty, isPincode)); // []The rest parameter accepts any number of validators, and each validator's value parameter gets its type from the Validator function type.
Quick Quiz
1. What does the return type void mean?
void says callers should not use the return value.
2. Which declaration is valid?
Optional and rest parameters must come after required ones; a rest parameter must be last.
3. In const rule: PriceRule = (price) => price * 0.8;, how does TypeScript know price is a number?
The declared function type tells TypeScript the parameter types of the arrow function.
4. How many signatures of an overloaded function can callers see?
The implementation signature is hidden; callers use the overload signatures.
Learn TypeScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
