Functions are where most application rules live: they calculate values, validate input, call APIs, transform data, and connect modules together. TypeScript helps by checking both sides of a function: what the caller is allowed to pass and what the function promises to return.
In plain JavaScript, a function can accidentally receive a string where a number was expected and the bug may appear much later. In TypeScript, parameter and return types make the contract visible at the function boundary, so mistakes are caught while coding.
function calculateDiscount(price: number, percent: number): number {
const discountAmount = (price * percent) / 100;
return price - discountAmount;
}
const finalPrice = calculateDiscount(1200, 15);
console.log(finalPrice); // 1020
// calculateDiscount("1200", 15);
// Error: the first argument must be a number.
Each parameter can have its own type annotation. The return type is written after the parameter list. TypeScript can often infer the return type, but explicit return types are useful for public functions, controller helpers, service functions, and shared utilities.
An explicit return type also catches missing return paths. If a function says it returns a number, TypeScript will complain when one branch returns nothing or returns a different type.
type InvoiceLine = {
label: string;
quantity: number;
unitPrice: number;
};
function getLineTotal(line: InvoiceLine): number {
return line.quantity * line.unitPrice;
}
function getInvoiceTotal(lines: InvoiceLine[]): number {
return lines.reduce((total, line) => total + getLineTotal(line), 0);
}
const total = getInvoiceTotal([
{ label: "Hosting", quantity: 1, unitPrice: 499 },
{ label: "Support", quantity: 2, unitPrice: 250 },
]);
console.log(total); // 999
Use ? when a parameter is truly optional. Inside the function, an optional parameter has the type you wrote plus undefined, so your code should handle the missing case before using it.
Default parameters are often better when the function has a sensible fallback. A default value keeps the call simple while still giving the function a real value to work with internally.
| Feature | Syntax | Best Use |
|---|---|---|
| Required parameter | name: string | The caller must provide the value. |
| Optional parameter | name?: string | The value may be missing and must be checked. |
| Default parameter | name = "Guest" | The function can provide a fallback itself. |
function greet(name: string, title = "Developer"): string {
return `Hello ${name}, ${title}`;
}
function formatPhone(countryCode: string, number?: string): string {
if (!number) {
return countryCode;
}
return `${countryCode}-${number}`;
}
console.log(greet("Asha")); // Hello Asha, Developer
console.log(greet("Asha", "Team Lead")); // Hello Asha, Team Lead
console.log(formatPhone("+91", "9876543210")); // +91-9876543210
A function type expression describes the shape of a function value. It is useful when you pass callbacks, store functions in objects, or expose a hook-style API where callers provide behavior.
The syntax looks like an arrow function: parameters on the left and the return type on the right. The parameter names are documentation for developers, while the parameter types are what TypeScript checks.
type ScorePredicate = (score: number) => boolean;
function filterScores(scores: number[], predicate: ScorePredicate): number[] {
return scores.filter(predicate);
}
const passed = filterScores([35, 72, 91], score => score >= 40);
const excellent = filterScores([35, 72, 91], score => score >= 90);
console.log(passed); // [72, 91]
console.log(excellent); // [91]
Objects often contain methods. You can type those methods directly in an object type or interface. This is common in service objects, repositories, validators, and UI event handlers.
When a method belongs to a typed object, TypeScript checks both the method implementation and every place that calls it. This keeps related data and behavior aligned.
type User = {
id: number;
name: string;
isActive: boolean;
};
type UserFormatter = {
label(user: User): string;
status(user: User): "active" | "inactive";
};
const formatter: UserFormatter = {
label(user) {
return `#${user.id} - ${user.name}`;
},
status(user) {
return user.isActive ? "active" : "inactive";
},
};
console.log(formatter.label({ id: 1, name: "Ravi", isActive: true }));
Some functions are called for their side effect rather than their returned value. Use void when a function does not return a useful value, such as a logger or event handler.
Use never for functions that cannot finish normally, such as functions that always throw an error. Use unknown for input from the outside world, then narrow it before using it.
function logMessage(message: string): void {
console.log(`[app] ${message}`);
}
function fail(message: string): never {
throw new Error(message);
}
function parseId(value: unknown): number {
if (typeof value === "number") {
return value;
}
if (typeof value === "string" && value.trim() !== "") {
return Number(value);
}
return fail("Invalid id value");
}
undefined.
void, never, and unknown help model side effects, impossible returns, and external input.
Explore 500+ free tutorials across 20+ languages and frameworks.