Introduction
In Part 1, we learned what TypeScript is and why it’s useful. In Part 2, we explored the differences between type and interface. In this part, we’ll cover one of the most powerful features of TypeScript:
Generics
Generics allow you to write reusable, type-safe, and flexible code that works with multiple data types without losing type information.
What Are Generics in TypeScript?
Generics enable you to create components (functions, classes, interfaces) that work with a variety of types rather than a single one.
Instead of hardcoding a type, you use a type variable (commonly T).
Simple Example (Without Generics)
function getFirstItem(arr: any[]) {
return arr[0];
}
❌ Problem: You lose type safety.
Same Example (With Generics)
function getFirstItem<T>(arr: T[]): T {
return arr[0];
}
✅ TypeScript now knows exactly what type is returned.
Why Use Generics?
Generics help you:
- Avoid
any - Preserve type information
- Write reusable logic
- Catch errors at compile time
- Build scalable and maintainable code
Generic Functions (Real-World Examples)
Example 1: API Response Handler
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
}
function fetchData<T>(response: ApiResponse<T>): T {
return response.data;
}
Usage
interface User {
id: number;
name: string;
}
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "Nishank" },
success: true,
message: "Fetched successfully",
};
const user = fetchData(userResponse);
✅ Fully type-safe API handling
Generic Interfaces
Generics are commonly used in interfaces to make them reusable.
Example: Pagination
interface Pagination<T> {
items: T[];
total: number;
page: number;
}
Usage
const usersPage: Pagination<User> = {
items: [{ id: 1, name: "Nishank" }],
total: 100,
page: 1,
};
Generic Classes
Example: Data Store
class Store<T> {
private data: T[] = [];
add(item: T) {
this.data.push(item);
}
getAll(): T[] {
return this.data;
}
}
Usage
const userStore = new Store<User>();
userStore.add({ id: 1, name: "Nishank" });
const users = userStore.getAll();
Generic Constraints
Sometimes you want to limit what types can be used with a generic.
Example: Object with ID Constraint
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
Multiple Generic Types
function mapValues<K, V>(key: K, value: V) {
return { key, value };
}
Real-World Use Cases
1. React Component Props
type SelectProps<T> = {
options: T[];
onSelect: (value: T) => void;
};
2. Form Handling
interface FormState<T> {
values: T;
isValid: boolean;
}
3. Utility Functions
function identity<T>(value: T): T {
return value;
}
Common Generic Naming Conventions
T– TypeK– KeyV– ValueE– ElementR– Return type
Generics vs Any
| Feature | any | Generics |
|---|---|---|
| Type safety | ❌ | ✅ |
| IntelliSense | ❌ | ✅ |
| Compile-time checks | ❌ | ✅ |
| Reusability | ❌ | ✅ |
Best Practices
- Prefer generics over
any - Keep generics simple and readable
- Use constraints when required
- Avoid over-engineering
Conclusion
Generics are a cornerstone of advanced TypeScript development. They allow you to write flexible, reusable, and type-safe code without sacrificing readability.
Mastering generics will significantly improve how you design APIs, utilities, and component libraries—especially in large-scale applications.
In the next part, you can explore TypeScript Utility Types and Advanced Patterns.
Happy coding!
