Functions in TypeScript
Functions in TypeScript
Functions are the building blocks of any TypeScript application. TypeScript extends JavaScript functions with powerful type annotations for parameters, return values, and function signatures. This allows you to catch errors early and write more maintainable code.
Function Type Annotations
The most basic way to add types to functions is by annotating parameters and return types:
// Function with parameter and return type annotations
function add(a: number, b: number): number {
return a + b;
}
// Arrow function with type annotations
const multiply = (a: number, b: number): number => {
return a * b;
};
// Shorthand arrow function
const subtract = (a: number, b: number): number => a - b;
// Function with no return value (void)
function logMessage(message: string): void {
console.log(message);
}
// Usage
const sum = add(5, 3); // sum: number = 8
const product = multiply(4, 7); // product: number = 28
logMessage('Hello, TypeScript!'); // Returns void
Optional Parameters
Optional parameters allow you to call a function without providing all arguments. Mark a parameter as optional by adding a question mark (?) after the parameter name.
// Function with optional parameter
function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}
// Usage
console.log(greet('Alice')); // "Hello, Alice!"
console.log(greet('Bob', 'Good morning')); // "Good morning, Bob!"
// Multiple optional parameters
function createUser(
username: string,
email?: string,
age?: number
): object {
return {
username,
email: email || 'N/A',
age: age || 0
};
}
const user1 = createUser('john_doe');
const user2 = createUser('jane_smith', 'jane@example.com');
const user3 = createUser('bob_jones', 'bob@example.com', 30);
Default Parameters
Default parameters provide a fallback value if no argument is passed. Unlike optional parameters, default parameters don't need the ? marker.
// Function with default parameter
function calculatePrice(
price: number,
taxRate: number = 0.1,
discount: number = 0
): number {
const taxAmount = price * taxRate;
const discountAmount = price * discount;
return price + taxAmount - discountAmount;
}
// Usage
console.log(calculatePrice(100)); // Uses defaults: 110
console.log(calculatePrice(100, 0.15)); // Custom tax: 115
console.log(calculatePrice(100, 0.1, 0.2)); // Custom tax and discount: 90
// Default parameters can reference previous parameters
function buildUrl(
protocol: string = 'https',
domain: string,
path: string = '/'
): string {
return `${protocol}://${domain}${path}`;
}
console.log(buildUrl('http', 'example.com'));
// "http://example.com/"
console.log(buildUrl(undefined, 'example.com', '/about'));
// "https://example.com/about"
Rest Parameters
Rest parameters allow a function to accept an indefinite number of arguments as an array. Use the spread operator (...) with a type annotation for the array.
// Function with rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
// Usage
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
console.log(sum()); // 0
// Rest parameters with other parameters
function createTeam(
teamName: string,
captain: string,
...members: string[]
): object {
return {
name: teamName,
captain,
members,
totalMembers: members.length + 1 // +1 for captain
};
}
const team = createTeam(
'Alpha Team',
'John',
'Alice',
'Bob',
'Charlie'
);
console.log(team);
// {
// name: 'Alpha Team',
// captain: 'John',
// members: ['Alice', 'Bob', 'Charlie'],
// totalMembers: 4
// }
// Rest parameters with different types
function logValues(prefix: string, ...values: (string | number)[]): void {
values.forEach(value => {
console.log(`${prefix}: ${value}`);
});
}
logValues('Item', 'apple', 42, 'banana', 100);
Function Type Expressions
You can define a function type separately and use it to type multiple functions:
// Define function type
type MathOperation = (a: number, b: number) => number;
// Functions matching the type
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;
const divide: MathOperation = (a, b) => b !== 0 ? a / b : 0;
// Function that accepts another function
function calculate(
a: number,
b: number,
operation: MathOperation
): number {
return operation(a, b);
}
// Usage
console.log(calculate(10, 5, add)); // 15
console.log(calculate(10, 5, subtract)); // 5
console.log(calculate(10, 5, multiply)); // 50
console.log(calculate(10, 5, divide)); // 2
// More complex function type
type Validator = (input: string) => {
isValid: boolean;
errors: string[];
};
const emailValidator: Validator = (input) => {
const errors: string[] = [];
if (!input.includes('@')) {
errors.push('Email must contain @');
}
if (input.length < 5) {
errors.push('Email too short');
}
return {
isValid: errors.length === 0,
errors
};
};
console.log(emailValidator('test@example.com')); // { isValid: true, errors: [] }
console.log(emailValidator('bad')); // { isValid: false, errors: [...] }
Function Overloads
Function overloads allow you to define multiple function signatures for the same function. This is useful when a function can be called with different parameter types or numbers of parameters.
// Overload signatures
function formatDate(date: Date): string;
function formatDate(timestamp: number): string;
function formatDate(year: number, month: number, day: number): string;
// Implementation signature (must be compatible with all overloads)
function formatDate(
dateOrYear: Date | number,
month?: number,
day?: number
): string {
if (dateOrYear instanceof Date) {
// Called with Date object
return dateOrYear.toISOString().split('T')[0];
} else if (month !== undefined && day !== undefined) {
// Called with year, month, day
return `${dateOrYear}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
} else {
// Called with timestamp
return new Date(dateOrYear).toISOString().split('T')[0];
}
}
// Usage - TypeScript enforces overload signatures
const date1 = formatDate(new Date()); // Date object
const date2 = formatDate(1707926400000); // Timestamp
const date3 = formatDate(2024, 2, 14); // Year, month, day
console.log(date1); // "2024-02-14"
console.log(date2); // "2024-02-14"
console.log(date3); // "2024-02-14"
// Another example: search function
function search(query: string): string[];
function search(query: string, limit: number): string[];
function search(query: string, limit: number, offset: number): string[];
function search(
query: string,
limit?: number,
offset?: number
): string[] {
// Simulate database search
const allResults = [
`Result 1 for "${query}"`,
`Result 2 for "${query}"`,
`Result 3 for "${query}"`,
`Result 4 for "${query}"`,
`Result 5 for "${query}"`
];
const start = offset || 0;
const end = limit ? start + limit : allResults.length;
return allResults.slice(start, end);
}
console.log(search('typescript')); // All results
console.log(search('typescript', 2)); // First 2 results
console.log(search('typescript', 2, 2)); // 2 results starting from index 2
Generic Functions
Generic functions allow you to write functions that work with multiple types while maintaining type safety:
// Generic function with type parameter
function identity<T>(value: T): T {
return value;
}
// Usage - type is inferred
const num = identity(42); // T is number
const str = identity('hello'); // T is string
const arr = identity([1, 2, 3]); // T is number[]
// Generic function with multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const pair1 = pair('age', 30); // [string, number]
const pair2 = pair(true, 'success'); // [boolean, string]
// Generic function with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = {
name: 'Alice',
age: 30,
email: 'alice@example.com'
};
const name = getProperty(person, 'name'); // string
const age = getProperty(person, 'age'); // number
// const invalid = getProperty(person, 'invalid'); // Error: invalid key
// Generic array operations
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
const numbers = [1, 2, 3, 4, 5];
const firstNum = first(numbers); // number | undefined
const lastNum = last(numbers); // number | undefined
const words = ['hello', 'world'];
const firstWord = first(words); // string | undefined
const lastWord = last(words); // string | undefined
this Parameter Type
TypeScript allows you to declare the type of this in a function:
interface User {
name: string;
age: number;
greet(this: User): void;
}
const user: User = {
name: 'Alice',
age: 30,
greet() {
// TypeScript knows 'this' is User
console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old`);
}
};
user.greet(); // "Hello, I'm Alice and I'm 30 years old"
// Prevent incorrect this binding
const greetFunction = user.greet;
// greetFunction(); // Error: The 'this' context is not assignable
// Using this in standalone functions
interface Database {
host: string;
port: number;
}
function connect(this: Database): string {
return `Connecting to ${this.host}:${this.port}`;
}
const db: Database = {
host: 'localhost',
port: 5432
};
// Call with proper context
console.log(connect.call(db)); // "Connecting to localhost:5432"
Callback Functions
Type callback functions properly to ensure type safety:
// Function accepting callback
function processArray(
items: number[],
callback: (item: number, index: number) => void
): void {
items.forEach((item, index) => {
callback(item, index);
});
}
// Usage
processArray([1, 2, 3, 4, 5], (num, idx) => {
console.log(`Item ${idx}: ${num * 2}`);
});
// Callback with return value
function filterArray<T>(
items: T[],
predicate: (item: T) => boolean
): T[] {
return items.filter(predicate);
}
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = filterArray(numbers, num => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8, 10]
// Async callback
function fetchData(
url: string,
onSuccess: (data: unknown) => void,
onError: (error: Error) => void
): void {
fetch(url)
.then(response => response.json())
.then(data => onSuccess(data))
.catch(error => onError(error));
}
fetchData(
'https://api.example.com/data',
(data) => console.log('Success:', data),
(error) => console.error('Error:', error.message)
);
Async Functions
Async functions in TypeScript return a Promise type:
// Async function with explicit return type
async function fetchUser(id: string): Promise<{
id: string;
name: string;
email: string;
}> {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
return {
id,
name: 'John Doe',
email: 'john@example.com'
};
}
// Using async function
async function main() {
try {
const user = await fetchUser('123');
console.log(user.name); // TypeScript knows user shape
} catch (error) {
console.error('Failed to fetch user:', error);
}
}
// Generic async function
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json() as T;
}
// Usage with type parameter
interface Post {
id: number;
title: string;
body: string;
}
async function loadPost(id: number): Promise<Post> {
return await fetchData<Post>(`https://api.example.com/posts/${id}`);
}
- Create a function
calculateDiscountthat takesprice: number, optionaldiscountPercent: number(default 10), and optionalmemberDiscount: number. Return the final price after applying discounts. - Create a generic function
groupBythat takes an array of items and a key selector function, returns an object grouping items by the selected key. - Create function overloads for
getValuethat accepts either a string key or a number index to retrieve values from an array or object. - Create an async function
fetchUserPoststhat takes a user ID and returns a Promise of an array of posts. - Test all your functions with sample data.
Summary
- Parameter types and return types make functions type-safe and self-documenting
- Optional parameters use
?and must come after required parameters - Default parameters provide fallback values and are automatically optional
- Rest parameters use
...to accept an indefinite number of arguments as an array - Function type expressions allow you to define reusable function types
- Function overloads define multiple call signatures for the same function
- Generic functions work with multiple types while maintaining type safety
- this parameter type ensures correct context binding in functions
- Async functions return
Promise<T>types automatically