Generic functions are one of TypeScript’s most powerful features for writing flexible, reusable, and type-safe code. Whether you’re building libraries, utilities, or application features, understanding generic functions will dramatically improve your TypeScript development skills.
In this guide, we’ll explore TypeScript generic functions from the ground up, with practical examples and clear explanations for beginners.
Table of Contents
- What are Generic Functions?
- Why Use Generic Functions?
- Basic Generic Function Syntax
- Multiple Type Parameters
- Constraining Type Parameters
- Generic Function Best Practices
- Practical Examples
- Common Gotchas and Solutions
- Next Steps
What are Generic Functions?
Generic functions allow you to write functions that can work with multiple types while maintaining type safety. Instead of committing to a specific type, you use a type parameter that gets replaced with a real type when the function is called.
Let’s start with a simple example:
function identity<T>(value: T): T {
return value;
}
// Usage
const numberResult = identity(42); // Type is number
const stringResult = identity('hello'); // Type is string
Code language: JavaScript (javascript)
In this example, T
is a type parameter that captures the type of the input value. The function then guarantees it will return a value of the same type.
Why Use Generic Functions?
Generic functions solve several common programming challenges:
- Type Safety: Maintain strong typing without duplicating code for different types
- Code Reusability: Write functions that work with any type while preserving type information
- Flexibility: Create functions that adapt to different data types at runtime
Basic Generic Function Syntax
Here’s the basic structure of a generic function:
function functionName<T>(parameter: T): T {
// Function implementation
return parameter;
}
Code language: JavaScript (javascript)
The <T>
after the function name declares a type parameter. You can use any valid identifier, but T
is commonly used as a convention.
Multiple Type Parameters
Generic functions can use multiple type parameters when needed:
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result = pair('hello', 42); // Type is [string, number]
Code language: HTML, XML (xml)
Constraining Type Parameters
Sometimes you want to restrict what types can be used with your generic function. Use the extends
keyword to add constraints:
interface Lengthwise {
length: number;
}
function getLength<T extends Lengthwise>(item: T): number {
return item.length;
}
// Works with strings
getLength('hello'); // Returns 5
// Works with arrays
getLength([1, 2, 3]); // Returns 3
// Error: number doesn't have a length property
// getLength(42);
Code language: PHP (php)
Generic Function Best Practices
1. Use Descriptive Type Parameter Names
While T
is common, use more descriptive names for clarity:
function merge<TFirst, TSecond>(obj1: TFirst, obj2: TSecond): TFirst & TSecond {
return { ...obj1, ...obj2 };
}
Code language: JavaScript (javascript)
2. Keep Type Parameters Minimal
Only use generic types when needed. If a parameter will always be the same type, don’t make it generic:
// Good: Uses generics only for the input type
function toArray<T>(item: T): T[] {
return [item];
}
// Bad: Unnecessary generic parameter
function toString<T>(value: T): string {
return String(value);
}
Code language: HTML, XML (xml)
3. Use Type Inference When Possible
TypeScript can often infer generic types automatically:
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
// Type parameters inferred automatically
const numbers = map([1, 2, 3], n => n.toString());
Code language: HTML, XML (xml)
Practical Examples
Generic Array Functions
function firstElement<T>(array: T[]): T | undefined {
return array[0];
}
function filter<T>(array: T[], predicate: (item: T) => boolean): T[] {
return array.filter(predicate);
}
// Usage
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = filter(numbers, n => n % 2 === 0);
Code language: HTML, XML (xml)
Generic State Container
class StateContainer<T> {
private state: T;
constructor(initialState: T) {
this.state = initialState;
}
getState(): T {
return this.state;
}
setState(newState: T): void {
this.state = newState;
}
}
// Usage
const numberState = new StateContainer(42);
const stringState = new StateContainer('hello');
Code language: JavaScript (javascript)
Common Gotchas and Solutions
1. Type Parameter Constraints
When working with object properties, always constrain your types appropriately:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Code language: HTML, XML (xml)
2. Generic Type Inference
Sometimes TypeScript needs help inferring types:
// May need explicit type parameter
const result = identity<string>('hello');
Code language: JavaScript (javascript)
3. Working with Generic Constraints
Be careful with nested properties in constraints:
interface Container<T> {
value: T;
}
function getValue<T>(container: Container<T>): T {
return container.value;
}
Code language: PHP (php)
Next Steps
Now that you understand TypeScript generic functions, you can:
- Refactor existing code to use generics for better type safety
- Create flexible utility functions that work with multiple types
- Build type-safe APIs and libraries
To deepen your TypeScript knowledge, consider exploring related topics like TypeScript Generic Classes or TypeScript Type Guards.
Generic functions are a fundamental part of TypeScript that enable you to write more maintainable, reusable, and type-safe code. Practice implementing these concepts in your projects, and you’ll quickly become proficient in using this powerful feature.