Skip to content

TypeScript - Functions

Functions are essential building blocks for creating readable, maintainable, and reusable code. They encapsulate a set of statements designed to perform a specific task. Organizing code into functions makes programs more logical and easier to manage.

In TypeScript, like JavaScript, functions are first-class citizens. They can be defined, passed around as arguments, returned from other functions, and assigned to variables.

TypeScript enhances JavaScript functions by adding explicit type annotations for parameters and return values, providing compile-time safety.

ConceptDescription
Defining a FunctionSpecifies the function’s name, parameters (with types), return type, and the block of code (body) to execute.
Calling a FunctionInvoking the function by its name followed by parentheses (), passing any required arguments.
Return ValuesFunctions can return a value using the return keyword. The type of the returned value should match the function’s declared return type. Functions that don’t return a value have a void return type.
Parameters & ArgumentsParameters are variables listed in the function definition. Arguments are the actual values passed to the function when it’s called.

TypeScript supports several ways to define functions:

function greet(name: string): string {
return `Hello, ${name}!`;
}
function logMessage(message: string): void {
console.log(message);
}
let greeting = greet("Alice"); // greeting is inferred as string
logMessage(greeting);

2. Function Expression (Anonymous Function)

Section titled “2. Function Expression (Anonymous Function)”

Assigning a function to a variable. The function itself might be anonymous.

const add = function(x: number, y: number): number {
return x + y;
};
let sum: number = add(10, 5);

A concise syntax for function expressions, especially useful for inline functions and callbacks. Arrow functions capture the this value of the enclosing context (lexical this).

const multiply = (x: number, y: number): number => {
return x * y;
};
// Concise body (single expression, implicit return)
const subtract = (x: number, y: number): number => x - y;
// Arrow function with no parameters
const logTime = (): void => { console.log(new Date()); };
// Arrow function with one parameter (parentheses optional)
const square = (x: number): number => x * x;
// const square = x => x * x; // Type inferred if possible, but explicit is often better
let product: number = multiply(4, 6);
logTime();

Parameters can be marked as optional by appending a ? to their name. Optional parameters must come after required parameters. If an optional parameter isn’t provided, its value is undefined.

function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
} else {
return firstName;
}
}
let name1 = buildName("Bob"); // Output: Bob
let name2 = buildName("Bob", "Adams"); // Output: Bob Adams
console.log(name1);
console.log(name2);

You can provide default values for parameters. If an argument isn’t provided for a default parameter (or if undefined is passed), the default value is used. Default parameters don’t strictly need to come after required parameters, but doing so often improves readability.

function calculateRate(price: number, rate: number = 0.05): number {
return price * rate;
}
let amount1 = calculateRate(1000); // Uses default rate 0.05 -> 50
let amount2 = calculateRate(1000, 0.1); // Uses provided rate 0.1 -> 100
console.log(amount1);
console.log(amount2);

Note: A parameter cannot be both optional (?) and have a default value.

Collect multiple arguments into a single array parameter using the rest syntax (...). The rest parameter must be the last parameter in the function definition and must be of an array type.

function sumAll(...numbers: number[]): number {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
let total1 = sumAll(1, 2, 3); // Output: 6
let total2 = sumAll(10, 20, 30, 40); // Output: 100
console.log(total1);
console.log(total2);

Function overloading allows defining multiple function signatures (parameter types and return type) for a single function name, followed by one implementation body. The implementation must have a signature compatible with all the overload signatures. TypeScript uses the overload signatures for type checking calls to the function.

// Overload signatures
function process(value: string): string;
function process(value: number): number;
function process(value: boolean): boolean;
// Implementation signature (must be compatible with overloads)
function process(value: string | number | boolean): string | number | boolean {
if (typeof value === 'string') {
return value.toUpperCase();
}
if (typeof value === 'number') {
return value * 2;
}
return !value;
}
let result1: string = process("data"); // Correctly inferred as string
let result2: number = process(10); // Correctly inferred as number
let result3: boolean = process(true); // Correctly inferred as boolean
console.log(result1); // Output: DATA
console.log(result2); // Output: 20
console.log(result3); // Output: false
// let errorResult = process({ a: 1 }); // Error: No overload matches this call.

Overloads are useful for functions that behave differently based on the types or number of arguments passed.

  • this Keyword: Behavior depends on how the function is called. Arrow functions have lexical this, while standard functions often depend on the call site (e.g., object method, standalone call). TypeScript provides ways to explicitly type this.
  • Recursion: Functions calling themselves. Requires a base case to prevent infinite loops.
  • Higher-Order Functions: Functions that accept other functions as arguments or return functions.
  • Generics: Creating reusable functions that can work with various types while maintaining type safety (covered in more advanced topics).
  • Always provide explicit type annotations for parameters and return values unless the type inference is obvious and trivial.
  • Prefer arrow functions for callbacks and short, simple functions due to concise syntax and lexical this.
  • Use const for function expressions unless you specifically need to reassign the variable.
  • Keep functions focused on a single task (Single Responsibility Principle).
  • Use function overloads when a function truly handles different argument patterns based on type, but avoid overly complex overload sets.