TypeScript Functions

Functions are fundamental building blocks in JavaScript and TypeScript. TypeScript enhances functions with optional static typing, helping you catch errors early and improve code quality. This guide will walk you through the essentials of TypeScript functions, from basic syntax to best practices.

Basic Function Syntax

In TypeScript, you can specify the types of function parameters and return values:

Basic Function

// Function with typed parameters and return type
function add(a: number, b: number): number {
  return a + b;
}

// Using the function
const sum = add(5, 3); // 8

The function add takes two parameters of type number and returns a value of type number. TypeScript ensures that you call this function with the correct parameter types.

Function Expressions

Function expressions work similarly, but you can explicitly specify the function type:

Function Expression

// Function expression with type annotation
const multiply: (x: number, y: number) => number = function (x, y) {
  return x * y;
};

// Arrow function version (more concise)
const divide = (x: number, y: number): number => x / y;

Optional Parameters

You can make parameters optional by adding a question mark (?) after the parameter name:

Optional Parameters

function greet(name: string, greeting?: string): string {
  if (greeting) {
    return `${greeting}, ${name}!`;
  }
  return `Hello, ${name}!`;
}

greet("Alice"); // "Hello, Alice!"
greet("Bob", "Welcome"); // "Welcome, Bob!"

Optional parameters must come after required parameters in the function signature.

Default Parameters

You can provide default values for parameters, which are used when the argument is omitted or undefined:

Default Parameters

function createMessage(name: string, role: string = "user"): string {
  return `${name} is a ${role}`;
}

createMessage("Alice"); // "Alice is a user"
createMessage("Bob", "administrator"); // "Bob is a administrator"

Rest Parameters

Rest parameters allow a function to accept an indefinite number of arguments as an array:

Rest Parameters

function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2); // 3
sum(1, 2, 3, 4, 5); // 15

Function Overloads

TypeScript allows function overloading, where you can define multiple function signatures for the same function:

Function Overloads

// Overload signatures
function formatValue(value: string): string;
function formatValue(value: number): string;
// Implementation signature
function formatValue(value: string | number): string {
  if (typeof value === "string") {
    return value.trim();
  }
  return value.toFixed(2);
}

formatValue("  hello  "); // "hello"
formatValue(3.14159); // "3.14"

Function overloads help you provide more specific type information to TypeScript when a function can handle different types of arguments.

Void Return Type

When a function doesn't return a value, you can use the void return type:

Void Return Type

function logMessage(message: string): void {
  console.log(message);
  // No return statement needed
}

Never Return Type

The never type represents values that never occur. It's used for functions that never return (e.g., they throw an exception or enter an infinite loop):

Never Return Type

function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // Do something forever
  }
}

Function Types as Variables

You can define a function type and use it for variables:

Function Types

// Define a function type
type MathOperation = (x: number, y: number) => number;

// Functions that match this type
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;

// Function that takes a function as argument
function calculate(operation: MathOperation, a: number, b: number): number {
  return operation(a, b);
}

calculate(add, 5, 3); // 8
calculate(subtract, 10, 4); // 6

Object Method Syntax

When defining methods in objects or classes, TypeScript provides a clean syntax:

Object Methods

interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

const calculator: Calculator = {
  add(a, b) {
    return a + b;
  },
  subtract(a, b) {
    return a - b;
  },
};

calculator.add(5, 3); // 8
calculator.subtract(10, 4); // 6

Contextual Typing

TypeScript can infer the types of function parameters from context in many situations:

Contextual Typing

// The array's forEach method provides type context
const numbers = [1, 2, 3, 4, 5];

// TypeScript knows 'num' is a number from context
numbers.forEach((num) => {
  console.log(num.toFixed(2));
});

Type Guards

Functions can act as type guards, which help TypeScript narrow down types:

Type Guards

function isString(value: any): value is string {
  return typeof value === "string";
}

function processValue(value: string | number) {
  if (isString(value)) {
    // TypeScript knows value is a string here
    console.log(value.toUpperCase());
  } else {
    // TypeScript knows value is a number here
    console.log(value.toFixed(2));
  }
}

Real-World Examples

Here are some examples of how to use TypeScript functions in common scenarios:

Event Handlers

Event Handlers

// Event handler function typed with TypeScript
function handleClick(event: MouseEvent): void {
  console.log("Button clicked at:", event.clientX, event.clientY);
  event.preventDefault();
}

// Adding an event listener
document.querySelector("button")?.addEventListener("click", handleClick);

Async Functions

Async Functions

// Async function with TypeScript types
async function fetchUserData(userId: string): Promise<User> {
  const response = await fetch(`/api/users/${userId}`);

  if (!response.ok) {
    throw new Error(`Failed to fetch user: ${response.statusText}`);
  }

  return response.json();
}

// Using the async function
interface User {
  id: string;
  name: string;
  email: string;
}

async function displayUser(userId: string): Promise<void> {
  try {
    const user = await fetchUserData(userId);
    console.log(`User: ${user.name} (${user.email})`);
  } catch (error) {
    console.error("Error:", error);
  }
}

Callback Patterns

Callback Patterns

// Function with a callback parameter
function processData(data: string[], callback: (item: string, index: number) => void): void {
  data.forEach((item, index) => {
    callback(item, index);
  });
}

// Using the function with a callback
const items = ["apple", "banana", "cherry"];
processData(items, (item, index) => {
  console.log(`Item ${index + 1}: ${item}`);
});

Best Practices for TypeScript Functions

1. Always specify return types

Even though TypeScript can infer return types, explicitly declaring them improves code readability and prevents accidental changes:

// Good: Explicit return type
function calculateArea(radius: number): number {
  return Math.PI * radius * radius;
}

// Avoid: Implicit return type
function calculateArea(radius: number) {
  return Math.PI * radius * radius;
}

2. Use function declarations for regular functions

Function declarations are hoisted and often more readable than function expressions for standard functions:

// Good: Function declaration
function createGreeting(name: string): string {
  return `Hello, ${name}!`;
}

// Less preferable for simple functions
const createGreeting = (name: string): string => {
  return `Hello, ${name}!`;
};

3. Use arrow functions for callbacks and short functions

Arrow functions are more concise and maintain the this context:

// Good: Arrow function for callback
[1, 2, 3].map((n: number): number => n * 2);

// Good: Arrow function for short operations
const double = (n: number): number => n * 2;

4. Prefer union types over overloads when possible

For simple cases, union types can be clearer than function overloads:

// Good: Union type
function formatValue(value: string | number): string {
  if (typeof value === "string") {
    return value.trim();
  }
  return value.toFixed(2);
}

// More complex but sometimes necessary: Overloads
function formatValue(value: string): string;
function formatValue(value: number): string;
function formatValue(value: string | number): string {
  // Implementation
}

5. Use default parameters instead of conditionals when possible

Default parameters make function signatures clearer:

// Good: Default parameter
function createUser(name: string, role: string = "user"): User {
  return { name, role };
}

// Avoid: Conditional inside function
function createUser(name: string, role?: string): User {
  return { name, role: role || "user" };
}

6. Keep functions focused (Single Responsibility Principle)

Functions should do one thing and do it well:

// Good: Two focused functions
function validateUser(user: User): boolean {
  // Only handle validation logic
}

function saveUser(user: User): void {
  // Only handle saving logic
}

// Avoid: One function doing multiple things
function validateAndSaveUser(user: User): boolean {
  // Handles both validation and saving
}

7. Document functions with JSDoc comments

JSDoc comments provide additional information that TypeScript can use:

/**
 * Calculates the distance between two points
 * @param {Point} point1 - The first point
 * @param {Point} point2 - The second point
 * @returns {number} The distance between the points
 */
function calculateDistance(point1: Point, point2: Point): number {
  const dx = point2.x - point1.x;
  const dy = point2.y - point1.y;
  return Math.sqrt(dx * dx + dy * dy);
}

Exercises

Exercise 1: Basic Function Types

Create a function called calculateTax that takes an amount (number) and a tax rate (number) and returns the tax amount. Use proper TypeScript type annotations.

Exercise 2: Function with Optional Parameters

Create a function called buildProfile that takes a person's name (required), age (required), and occupation (optional). The function should return a formatted string with the person's information.

Exercise 3: Function Types and Callbacks

Create a function processNumbers that takes an array of numbers and a callback function. The callback should receive a number and return a number. The processNumbers function should apply the callback to each number in the array and return a new array with the results.

Summary

TypeScript functions enhance JavaScript functions with static typing, making your code more robust and maintainable. Key points covered in this guide include:

  1. Basic Syntax: TypeScript allows you to specify parameter types and return types for functions.
  2. Optional and Default Parameters: Make parameters optional with ? or provide default values with =.
  3. Rest Parameters: Handle variable numbers of arguments with rest parameters (...args).
  4. Function Types: Define and reuse function type signatures for consistent typing.
  5. Type Guards: Create functions that help TypeScript narrow down types in conditional blocks.
  6. Best Practices:
    • Always specify return types
    • Use function declarations for regular functions
    • Use arrow functions for callbacks
    • Keep functions focused on a single responsibility
    • Document functions with JSDoc comments

By applying these principles, you can write TypeScript functions that are more reliable, easier to understand, and simpler to maintain.