Functions with Types
TypeScript Programming
Functions with Types
Functions are the fundamental building blocks of any TypeScript application. TypeScript enhances JavaScript functions with type annotations for parameters and return values, ensuring that functions are called with the correct arguments and that their results are used appropriately. This compile-time checking eliminates a huge class of bugs that would otherwise only surface at runtime. TypeScript also supports optional parameters, default values, rest parameters, and function overloads, giving you precise control over how your functions can be invoked.
Parameter and Return Types
Type annotations are placed after each parameter name and after the parameter list for the return type. While TypeScript can often infer return types, explicitly declaring them is considered best practice because it documents intent and catches accidental changes.
// Explicit parameter and return types
function add(a: number, b: number): number {
return a + b;
}
// Arrow function with types
const multiply = (x: number, y: number): number => x * y;
// String function
function formatName(first: string, last: string): string {
return `${first} ${last}`;
}
// Boolean return
function isEven(n: number): boolean {
return n % 2 === 0;
}
// Void return — function does not return a value
function logInfo(message: string): void {
console.log(`[INFO] ${message}`);
}
Optional and Default Parameters
Optional parameters are marked with a ? after the parameter name. They must come after all required parameters. Default parameters provide a fallback value and are inherently optional.
// Optional parameter
function greet(name: string, greeting?: string): string {
return `${greeting || "Hello"}, ${name}!`;
}
greet("Alice"); // "Hello, Alice!"
greet("Alice", "Welcome"); // "Welcome, Alice!"
// Default parameter
function createUser(
name: string,
role: string = "viewer",
active: boolean = true
): { name: string; role: string; active: boolean } {
return { name, role, active };
}
createUser("Bob"); // { name: "Bob", role: "viewer", active: true }
createUser("Charlie", "admin"); // { name: "Charlie", role: "admin", active: true }
createUser("Diana", "editor", false); // { name: "Diana", role: "editor", active: false }
Rest Parameters
Rest parameters collect all remaining arguments into a typed array. They must be the last parameter in the function signature.
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
// Combine regular and rest parameters
function logTagged(tag: string, ...messages: string[]): void {
messages.forEach(msg => console.log(`[${tag}] ${msg}`));
}
logTagged("DEBUG", "Starting process", "Loading config", "Ready");
Function Types
You can define the type of a function variable using function type expressions. This is useful for callbacks, higher-order functions, and defining interfaces that include methods.
// Function type expression
let calculator: (a: number, b: number) => number;
calculator = (x, y) => x + y;
console.log(calculator(5, 3)); // 8
calculator = (x, y) => x * y;
console.log(calculator(5, 3)); // 15
// Type alias for a function type
type Predicate = (value: number) => boolean;
function filterNumbers(nums: number[], test: Predicate): number[] {
return nums.filter(test);
}
const evens = filterNumbers([1, 2, 3, 4, 5, 6], n => n % 2 === 0);
console.log(evens); // [2, 4, 6]
Function Overloads
Function overloads let you define multiple call signatures for a single function. The implementation signature must be compatible with all overload signatures. This is useful when a function behaves differently based on the types or number of arguments.
// Overload signatures
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
// Implementation signature
function format(value: string | number | Date): string {
if (typeof value === "string") {
return value.trim().toUpperCase();
} else if (typeof value === "number") {
return value.toFixed(2);
} else {
return value.toISOString().split("T")[0];
}
}
console.log(format(" hello ")); // "HELLO"
console.log(format(3.14159)); // "3.14"
console.log(format(new Date(2024, 0, 1))); // "2024-01-01"
Tip: Prefer union types over function overloads when the implementation logic does not differ significantly between signatures. Overloads add complexity and should be reserved for cases where the return type depends on the input type.
Key Takeaways
- Annotate function parameters and return types explicitly for clarity and safety.
- Use
?for optional parameters and provide default values with=. - Rest parameters (
...args: T[]) collect variable arguments into a typed array. - Function type expressions like
(a: number) => stringdescribe the shape of callable values. - Function overloads define multiple call signatures when behavior varies by argument type.