Mastering Advanced TypeScript Generics for Scalable Code Architecture
Learn how to use advanced TypeScript generics to build scalable, reusable, and type-safe code architectures with beginner-friendly examples and explanations.
TypeScript generics are powerful tools that allow you to write flexible and reusable code while keeping type safety intact. In this tutorial, we'll go beyond basic generics and explore advanced concepts such as constrained generics, generic utility types, and generic classes to help you build scalable code architectures.
Let's start with a quick recap: generics let you define a function, class, or interface that works with any data type, which you specify when using it. This reduces redundancy and increases flexibility.
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity<string>('hello');### Constraining Generics Sometimes, you want your generic type to follow certain rules or extend a particular type. This is done using `extends`. For example, let's create a function that only accepts objects with a `length` property.
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log('Length:', item.length);
return item;
}
logLength('Hello!'); // Length: 6
logLength([1, 2, 3]); // Length: 3### Generic Interfaces and Classes You can also create generic interfaces and classes. This is very useful for building scalable architectures such as data handling, APIs, or UI components.
interface ApiResponse<T> {
data: T;
status: number;
}
class Repository<T> {
private items: T[] = [];
add(item: T) {
this.items.push(item);
}
getAll(): T[] {
return this.items;
}
}
const numberRepo = new Repository<number>();
numberRepo.add(1);
numberRepo.add(2);
console.log(numberRepo.getAll()); // [1, 2]### Using Utility Generics TypeScript provides some built-in generic utility types such as `Partial`, `Readonly`, and `Pick` which are helpful in real-world applications to create scalable and maintainable code.
interface User {
id: number;
name: string;
email: string;
}
function updateUser(user: User, fieldsToUpdate: Partial<User>): User {
return { ...user, ...fieldsToUpdate };
}
const user1: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
const updatedUser = updateUser(user1, { email: 'alice@newdomain.com' });
console.log(updatedUser);### Generic Constraints with keyof You can also enforce that a generic type is a key of a specific interface using the `keyof` constraint. This helps make functions more precise and safe.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: 'Bob', age: 25 };
console.log(getProperty(person, 'name')); // Bob
// console.log(getProperty(person, 'unknown')); // Error: Argument of type '"unknown"' is not assignable### Summary Advanced generics empower you to write scalable and type-safe code architectures in TypeScript. By leveraging constraints, generic classes and interfaces, utility types, and keyof, you can build reusable components and functions that adapt to various data structures. Practice these patterns and integrate them into your projects to maximize maintainability and scalability.