TechLead
Lesson 4 of 8
5 min read
TypeScript

Functions in TypeScript

Type function parameters, return values, overloads, and understand function type expressions

Function Type Annotations

// Basic function with types
function add(a: number, b: number): number {
  return a + b;
}

// Arrow function with types
const multiply = (a: number, b: number): number => {
  return a * b;
};

// Return type inference
function subtract(a: number, b: number) {
  return a - b;  // Return type inferred as number
}

// Void return type
function logMessage(message: string): void {
  console.log(message);
}

// Never return type (throws or infinite loop)
function fail(message: string): never {
  throw new Error(message);
}

Optional and Default Parameters

// Optional parameter (must come after required)
function greet(name: string, greeting?: string): string {
  return `${greeting || "Hello"}, ${name}!`;
}

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

// Default parameter
function greetWithDefault(name: string, greeting: string = "Hello"): string {
  return `${greeting}, ${name}!`;
}

greetWithDefault("Alice");        // "Hello, Alice!"
greetWithDefault("Bob", "Hi");    // "Hi, Bob!"

// Default with type inference
function createUser(name: string, role = "user") {
  // role is inferred as string
  return { name, role };
}

Rest Parameters

// Rest parameters
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, num) => acc + num, 0);
}

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

// Rest with other parameters
function buildName(firstName: string, ...restOfName: string[]): string {
  return firstName + " " + restOfName.join(" ");
}

buildName("John", "Paul", "Jones");  // "John Paul Jones"

// Typed rest parameters with tuples
function readButtonInput(...args: [string, number, ...boolean[]]): void {
  const [name, version, ...flags] = args;
  console.log(name, version, flags);
}

Function Type Expressions

// Function type expression
type MathOperation = (a: number, b: number) => number;

const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;

// Using in other types
interface Calculator {
  add: MathOperation;
  subtract: MathOperation;
  multiply: MathOperation;
  divide: MathOperation;
}

// Callback function types
type Callback = (error: Error | null, result?: string) => void;

function fetchData(url: string, callback: Callback): void {
  // ...
  callback(null, "data");
}

// Array methods with typed callbacks
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n: number): number => n * 2);

// Type alias for event handler
type ClickHandler = (event: MouseEvent) => void;

Generic Functions

// Basic generic function
function identity<T>(arg: T): T {
  return arg;
}

identity<string>("hello");  // explicit type
identity(42);               // inferred as number

// Generic with constraints
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

getLength("hello");     // 5
getLength([1, 2, 3]);   // 3
// getLength(123);      // Error: number doesn't have length

// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const result = pair("hello", 42);  // [string, number]

// Generic with default type
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

createArray(3, "x");   // string[] (explicit)
createArray(3, 42);    // number[] (inferred)
createArray<>(3, "x"); // string[] (default)

Function Overloads

// Function overloads
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
  if (month !== undefined && day !== undefined) {
    return new Date(yearOrTimestamp, month - 1, day);
  }
  return new Date(yearOrTimestamp);
}

makeDate(1609459200000);     // From timestamp
makeDate(2024, 1, 15);       // From year, month, day
// makeDate(2024, 1);        // Error: no matching overload

// Overloads with different return types
function getValue(key: "name"): string;
function getValue(key: "age"): number;
function getValue(key: "active"): boolean;
function getValue(key: string): string | number | boolean {
  const data: Record<string, string | number | boolean> = {
    name: "Alice",
    age: 30,
    active: true
  };
  return data[key];
}

const name = getValue("name");     // string
const age = getValue("age");       // number
const active = getValue("active"); // boolean

this Parameter

// Explicit this type
interface User {
  name: string;
  greet(this: User): string;
}

const user: User = {
  name: "Alice",
  greet() {
    return `Hello, I'm ${this.name}`;
  }
};

user.greet();  // "Hello, I'm Alice"

// const callback = user.greet;
// callback();  // Error: 'this' context of type 'void'

// this in class methods
class Counter {
  count = 0;

  // Arrow function preserves 'this'
  increment = () => {
    this.count++;
  };

  // Regular method needs binding
  decrement() {
    this.count--;
  }
}

Practical Examples

// API fetch function
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// Event handler
function handleClick(event: React.MouseEvent<HTMLButtonElement>): void {
  console.log("Clicked:", event.currentTarget);
}

// Debounce utility
function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout>;

  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), wait);
  };
}

// Higher-order function
function withLogging<T extends (...args: any[]) => any>(fn: T): T {
  return ((...args) => {
    console.log("Calling with:", args);
    const result = fn(...args);
    console.log("Result:", result);
    return result;
  }) as T;
}

Key Takeaways

  • • Type both parameters and return values
  • • Use ? for optional parameters
  • • Use rest parameters with typed arrays
  • • Generic functions enable type-safe reusability
  • • Function overloads handle multiple signatures
  • • Be explicit with this when needed

Continue Learning