TypeScript generics allow you to write flexible, reusable code that works with multiple data types while maintaining type safety. If you’ve ever found yourself writing nearly identical code for different data types, generics are your solution.
In this comprehensive guide, we’ll explore TypeScript generics from the ground up, making complex concepts approachable for beginners.
Table of Contents
- Why Use Generics?
- Basic Generic Syntax
- Generic Interfaces
- Generic Constraints
- Multiple Type Parameters
- Generic Classes
- Best Practices
- Common Use Cases
- Troubleshooting Common Issues
- Conclusion
Why Use Generics?
Imagine you’re building a function that needs to work with different types of data – strings, numbers, or custom objects. Without generics, you’d need to write separate functions for each type or use the any
type, losing type safety. Generics solve this problem elegantly.
Basic Generic Syntax
Let’s start with a simple example:
function identity<T>(value: T): T {
return value;
}
// Usage
let result1 = identity<string>("Hello"); // Type is string
let result2 = identity<number>(42); // Type is number
Code language: JavaScript (javascript)
Here, <T>
is a type parameter that allows us to capture the type provided by the user. The function takes a value of type T and returns a value of the same type.
Generic Interfaces
You can also create generic interfaces for flexible object structures:
interface Container<T> {
value: T;
getValue(): T;
}
class NumberContainer implements Container<number> {
constructor(public value: number) {}
getValue(): number {
return this.value;
}
}
Code language: PHP (php)
Generic Constraints
Sometimes you want to limit what types can be used with your generic. You can do this using constraints:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(item: T): void {
console.log(`Length: ${item.length}`);
}
// This works
logLength("Hello"); // strings have length
logLength([1, 2, 3]); // arrays have length
// This would error
// logLength(123); // numbers don't have length
Code language: JavaScript (javascript)
Multiple Type Parameters
Generics can work with multiple type parameters:
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let result = pair<string, number>("Hello", 42);
Code language: HTML, XML (xml)
Generic Classes
Classes can also be generic, allowing for flexible yet type-safe class implementations:
class Queue<T> {
private data: T[] = [];
push(item: T): void {
this.data.push(item);
}
pop(): T | undefined {
return this.data.shift();
}
}
let numberQueue = new Queue<number>();
numberQueue.push(10);
numberQueue.push(20);
Code language: JavaScript (javascript)
Best Practices
Use Descriptive Type Parameter Names
- Use
T
for general types - Use
K
for key types - Use
V
for value types - Use descriptive names for clarity (e.g.,
TEntity
for entity types)
- Use
Keep Constraints Simple
- Don’t overcomplicate generic constraints
- Use interfaces for complex constraints
Provide Type Inference
- Let TypeScript infer types when possible
- Explicitly specify types when inference isn’t clear
Common Use Cases
API Responses
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
name: string;
}
function fetchUser(): Promise<ApiResponse<User>> {
// Implementation
return Promise.resolve({
data: { id: 1, name: "John" },
status: 200,
message: "Success"
});
}
Code language: PHP (php)
State Management
class State<T> {
private value: T;
constructor(initial: T) {
this.value = initial;
}
getValue(): T {
return this.value;
}
setValue(newValue: T): void {
this.value = newValue;
}
}
Code language: JavaScript (javascript)
Troubleshooting Common Issues
Type Inference Problems
- When type inference fails, explicitly specify the type parameters
- Use the
as
keyword for type assertions when necessary
Constraint Errors
- Ensure your types satisfy all constraints
- Check interface implementations carefully
Generic Type Parameter Scope
- Remember that type parameters are scoped to their declaration
- Use consistent naming across related generics
Conclusion
Generics are a powerful feature in TypeScript that enable you to write flexible, reusable code while maintaining type safety. By following the patterns and practices outlined in this guide, you can effectively use generics to improve your TypeScript applications.
Start simple with basic generic functions and gradually incorporate more complex patterns as you become comfortable with the concept. Remember that the goal is to write maintainable, type-safe code that can work with multiple data types efficiently.