Generic functions allow creating reusable functions that can work with multiple types while maintaining type safety. They use type parameters denoted by <T>. Example: function identity<T>(arg: T): T { return arg; }. Generic functions provide type inference and type safety while being flexible.
Higher-order functions are functions that take other functions as parameters or return functions. Example: function map<T, U>(arr: T[], fn: (item: T) => U): U[] { return arr.map(fn); }. They're fundamental for functional programming and enable composition and abstraction.
Function type assertions allow you to tell TypeScript that a function is of a specific type. Example: const myFunc = ((x: string) => parseInt(x)) as (x: string) => number;. They should be used sparingly as they bypass TypeScript's type checking.
Function type intersections combine multiple function types into one that must satisfy all types. Example: type Combined = ((x: string) => string) & ((x: number) => number);. The resulting function must handle both string and number inputs with corresponding return types.
Async functions are typed with Promise<T> where T is the type of the resolved value. Example: async function getData(): Promise<string> { const response = await fetch('api'); return response.text(); }. TypeScript ensures type safety for async/await operations.
Function type unions allow a function to have multiple possible types. Example: type StringOrNumberFunc = ((x: string) => string) | ((x: number) => number);. When using such functions, TypeScript ensures type safety by requiring type checking or overloads.
Optional parameters are marked with a '?' after the parameter name. They must come after required parameters. Example: function greet(name: string, greeting?: string) { return greeting ? `${greeting}, ${name}!` : `Hello, ${name}!`; }. Optional parameters can be omitted when calling the function.
Default parameters provide fallback values for parameters that are not passed to the function. Example: function greet(name: string, greeting: string = 'Hello') { return `${greeting}, ${name}!`; }. Default parameters can be of any type and can reference other parameters.
Callbacks can be typed using function types or interfaces. Example: interface Callback { (error: Error | null, result?: string): void; } function fetchData(callback: Callback) {}. This ensures type safety when working with asynchronous operations and event handlers.
Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. Example: function curry<T,U,V>(f: (x: T, y: U) => V): (x: T) => (y: U) => V { return x => y => f(x, y); }. This enables partial application and function composition.
Function decorators are typed as functions that take a function descriptor and return a new descriptor or value. Example: function logged(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`Calling ${propertyKey}`); return original.apply(this, args); }; }
TypeScript can infer function return types and sometimes parameter types based on usage and context. Example: function map<T, U>(array: T[], func: (item: T) => U): U[] { return array.map(func); }. TypeScript infers the return type U[] based on the mapping function.
TypeScript offers several ways to define function types: 1) Using interface: interface Func { (x: number): number; }, 2) Type alias: type Func = (x: number) => number, 3) Arrow function syntax: let func: (x: number) => number, 4) Method signature in object type: { method(x: number): number }. Each approach has specific use cases and benefits.
Rest parameters allow functions to accept an indefinite number of arguments as an array. They're denoted by '...' before the parameter name. Example: function sum(...numbers: number[]): number { return numbers.reduce((total, n) => total + n, 0); }. Rest parameters must be the last parameter in a function.
Function overloading involves declaring multiple function signatures followed by a single implementation that handles all cases. Example: function add(a: string, b: string): string; function add(a: number, b: number): number; function add(a: any, b: any): any { return a + b; }. The implementation must be compatible with all overload signatures.
Arrow functions provide a concise syntax for function expressions and lexically bind 'this'. Example: let add = (a: number, b: number): number => a + b;. They differ from regular functions in that they don't have their own 'this', arguments object, super, or new.target bindings.
Generator functions return iterables using the function* syntax and yield keyword. Example: function* range(start: number, end: number): Generator<number> { for(let i = start; i <= end; i++) yield i; }. They're useful for creating iterables and handling sequences.
Call signatures define how a function can be called, while construct signatures define how a function can be used with 'new'. Example: interface CallableConstructable { (x: string): string; new(x: string): object; }. This allows typing functions that can be both called and constructed.
Function properties are typed using regular property syntax on function types. Example: interface FunctionWithProps { (x: number): number; defaultValue: number; }. This allows creating functions with additional properties or methods.
Function type constraints limit what types can be used with generic functions. Example: function longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b; }. This ensures the generic type T has a length property.
TypeScript allows typing 'this' parameter as the first parameter in function declarations. Example: function example(this: void) {} ensures the function doesn't use 'this'. For methods: function example(this: MyClass) {} ensures 'this' is of type MyClass. This helps catch incorrect 'this' usage.
Method chaining involves returning this from methods to enable consecutive calls. Example: class Calculator { private value: number = 0; add(n: number): this { this.value += n; return this; } multiply(n: number): this { this.value *= n; return this; } }
Function composition combines multiple functions into a single function, applying them in sequence. Example: const compose = <T,U,V>(f: (x: U) => V, g: (x: T) => U) => (x: T): V => f(g(x));. This enables building complex functions from simpler ones.
Contextual typing allows TypeScript to infer types based on the context where a function is used. Example: const numbers = [1, 2, 3].map(n => n * 2); TypeScript infers that the arrow function parameter n is a number based on the array's type.
Event handlers are typically typed using the appropriate event interface. Example: function handleClick(event: React.MouseEvent<HTMLButtonElement>) { console.log(event.currentTarget); }. This ensures type safety when working with DOM events and framework-specific event systems.